Add text input layer: POST /input/text and GET /input/{input_id} (#546)#595
Add text input layer: POST /input/text and GET /input/{input_id} (#546)#595abhishek-8081 wants to merge 2 commits into
Conversation
chetanr25
left a comment
There was a problem hiding this comment.
I tested both APIs and they work well. You've also covered the validations, which is great. From a functionality perspective, this PR is good to go.
That said, I have a few changes in mind, so I've requested changes mainly to keep our codebase maintainable.
|
|
||
| from app.api.routes import forms, templates, weather, zipcode, jobs, system | ||
| from app.api.routes import forms, jobs, system, templates, weather, zipcode | ||
| from app.api.routes import input as input_routes |
There was a problem hiding this comment.
Just a small change.
As per the Discussion comment, we chose to use non-Alias approach.
To keep things constant through-out the codebase, we can follow the approach which we discussed in the Community discussion
| from app.api.routes import input as input_routes | |
| from app.api.routes import input |
| api_router.include_router(weather.router, prefix=API_PREFIX) | ||
| api_router.include_router(zipcode.router, prefix=API_PREFIX) No newline at end of file | ||
| api_router.include_router(zipcode.router, prefix=API_PREFIX) | ||
| api_router.include_router(input_routes.router, prefix=API_PREFIX) No newline at end of file |
There was a problem hiding this comment.
| api_router.include_router(input_routes.router, prefix=API_PREFIX) | |
| api_router.include_router(input.router, prefix=API_PREFIX) |
|
|
||
| words = narrative.split() | ||
| if len(words) < 10: | ||
| return JSONResponse( |
There was a problem hiding this comment.
We can use validation_error_handler() in order to throw 422 error. In this way we can avoid duplication and keep our codebase more maintainable
Reference:
FireForm/app/core/errors/handlers.py
Lines 20 to 37 in 7224f68
| created_at=now, | ||
| updated_at=now, | ||
| ) | ||
| db.add(record) |
There was a problem hiding this comment.
We should move this to repository.py and use that function here. Let's keep in mind that we will be using repositories.py for all database operations
|
|
||
|
|
||
| class TextInputRequest(BaseModel): | ||
| model_config = ConfigDict() |
There was a problem hiding this comment.
Just curious, Do you think we would need model_config in Text input?
I think we can skip. Let me know if we are using model_config for anything else (now or in the future)
| model_config = ConfigDict() |
|
|
||
|
|
||
| class TextInputResponse(BaseModel): | ||
| model_config = ConfigDict() |
There was a problem hiding this comment.
Same goes here
| model_config = ConfigDict() |
There was a problem hiding this comment.
I think there is too much going on under this file (keeping in mind that it's a routes file)
We should keep all the service logic under services and routes files would be responsible only to call those functions and expose it as API.
Right now app/api/routes/input.py is serving as:
- Logic and validation layer (which services/ should be handling)
- Database operations (which repository.py is responsible for)
All make sense, thanks for the thorough review - agree on all of these. Plan: DB ops (create/get input) move to repositories.py, the word-count 422 reuses One small check on the service layer: for text input the logic is pretty thin (length/ |
|
I always prefer keeping files as small as possible, with each file responsible only for the functionality it's supposed to handle. For example, The overall flow should be like: Since the scope of splitting this into services is relatively small, I think either approach would work. |
|
I'll check it soon. |
|
Addressed all the review comments: DB ops moved to repositories.py (create_input / |
Closes #546.
Adds the text input layer — POST /api/v1/input/text and GET /api/v1/input/{input_id} — building on the Input model from #544. Fully synchronous (no Celery); text is stored and marked
readyimmediately. No migration needed (the inputs table already exists).Validation follows the contract, which specifies the narrative minimum two ways —
minLength: 20in the schema and "at least 10 words" in the description and 422 example. Enforces both:min_length=20via Pydantic (auto-422 through the #543 handler) plus a 10-word check in the route. The word-count 422 is emitted as a direct JSONResponse matching the #543 validation-error shape exactly ({error_code, message, validation_errors:[{field, issue, value}]}) — it doesn't use AppError because AppError doesn't carry a validation_errors list, and the goal is a single consistent 422 format. The >50,000-char limit raises a 413 NARRATIVE_TOO_LONG (manual AppError, since the contract wants 413 not 422). GET on an unknown id returns the #543 envelope with INPUT_NOT_FOUND.retry_after_secondson the GET response is null for ready text inputs; the poll interval (5s, from the contract's voice example) is centralized as INPUT_POLL_INTERVAL_SECONDS in config.py, distinct from the existing RETRY_AFTER_SECONDS (the 503 hint) — it'll be exercised properly by the voice path in #547.Tests: 127 passing — 15 new in test_v1_input.py covering the 201 happy path, character/word counts, optional-field roundtrip, transcript storage, the 413 boundary (both > and exactly 50k), both 422 paths with exact shape assertions, and GET 200/404.