Architecture
Service architecture, components, and data flow
This page describes how pycli is structured internally and how its two surfaces — the CLI and the HTTP API — relate to each other. Understanding the architecture helps you predict how requests flow through the system, where failures can occur, and why certain design boundaries exist. Whether you are integrating with the HTTP API or scripting deployments through the CLI, the mental model here will inform how you debug and extend your workflows.
pycli exposes two independent surfaces that share no runtime coupling. Each component below plays a distinct role in the system.
| Component | Type | Role |
|---|---|---|
| pycli CLI | Click application | The command-line entry point. Accepts structured flags and arguments to trigger deployment and configuration operations on your behalf. |
deploy command | CLI subcommand | Executes a stack deployment against a target environment. Accepts a required --region flag (enum-valued) and an optional --dry-run boolean flag to preview changes without applying them. |
config set subcommand | CLI subcommand | Writes a single configuration key-value pair. Accepts a --value option and a positional key argument, letting you update runtime configuration from a script or terminal. |
| HTTP API | FastAPI application (api:app) | The programmable surface of pycli. Exposes a REST interface for order management. This is the component you call directly from your application code or HTTP client. |
POST /orders | API endpoint | Creates a new order. Accepts a JSON body validated against the CreateOrderBody Pydantic schema, ensuring your payload is well-formed before processing. |
GET /orders | API endpoint | Returns a list of orders. Supports query parameters such as status and limit so you can filter results without fetching the full dataset. |
GET /orders/{order_id} | API endpoint | Retrieves a single order by its identifier. Use this when you need to inspect the current state of a specific order. |
DELETE /orders/{order_id} | API endpoint | Cancels an order by its identifier. This is a destructive operation; the order identified by {order_id} will no longer be active after a successful response. |
Pydantic CreateOrderBody | Schema / validation layer | Defines and enforces the shape of order creation requests. Because this schema is JSON-Schema-checkable, your tooling can validate payloads before they ever reach the API, reducing round-trips caused by malformed requests. |
The following trace walks through creating an order — one of the most complete paths through the system — so you can see how each layer participates.
End-to-end: Create an order
-
You send an HTTP POST request to
https://api.example.com/orderswith a JSON body containing the fields required byCreateOrderBody(for example,amountandcurrency). -
The FastAPI application (
api:app) receives the request. FastAPI routes the request to thePOST /ordershandler based on the method and path. -
Pydantic validates the request body. Before your handler logic runs, FastAPI passes the raw JSON through the
CreateOrderBodyschema. If any required field is missing or the wrong type, Pydantic rejects the request immediately and returns a422 Unprocessable Entityresponse with details about the validation failure. Your application code never runs in this case. -
The handler processes the validated order and returns a response — typically the created order object with an assigned
order_id. -
You receive the response and can use the returned
order_idin subsequent calls, such asGET /orders/{order_id}to poll status orDELETE /orders/{order_id}to cancel.
Contrast: CLI data flow
When you run pycli deploy --region us-east-1 production, control flows through the Click application instead. Click parses your flags and arguments, enforces that --region is one of the allowed enum values, and invokes the deploy logic directly in the same process. There is no HTTP layer involved — the CLI operates as a local command rather than a client to the HTTP API.
Two separate surfaces instead of one
pycli exposes a CLI and an HTTP API as independent surfaces rather than having the CLI act as a thin wrapper around the HTTP API. This means each surface can be optimised for its primary consumer: the CLI is shaped for human operators scripting deployments, while the API is shaped for programmatic integration. The trade-off is that you cannot, for example, use a single pycli CLI call to manage orders — those operations are intentionally API-only.
FastAPI as the API framework
FastAPI was chosen because it couples directly with Pydantic for request validation and generates JSON Schema from your model definitions automatically. This means the CreateOrderBody shape is checkable by external tooling (linters, contract tests) without additional annotation work. An alternative such as Flask would have required a separate schema library to achieve the same level of body validation.
Pydantic schema on POST only
Only the POST /orders endpoint uses a structured Pydantic body model. Read and delete operations (GET, DELETE) identify resources through path parameters (order_id) and query parameters (status, limit), which FastAPI validates natively without a dedicated schema class. This keeps the model surface minimal — you only define a schema where the input shape is genuinely complex.
Click for the CLI
Click was chosen for the CLI because it provides built-in support for enum-valued flags and boolean flags with minimal boilerplate. These are the two structural shapes that matter most for the deploy and config set commands. The alternative, argparse, would have required more manual type-checking code to enforce the same constraints.
The CLI and API do not share functionality
Because the two surfaces are independent, there is no CLI command for order management and no HTTP endpoint for deployments. If your workflow needs both — for example, deploying a stack and then creating an order in the same automated script — you must coordinate both surfaces yourself, either by shelling out to pycli and making HTTP calls from the same script, or by wrapping both in a higher-level orchestration layer.
No authentication described in available documentation
Validation errors surface as 422, not 400
Because validation is handled by Pydantic through FastAPI, malformed request bodies return 422 Unprocessable Entity rather than the 400 Bad Request that some HTTP clients expect. If your error-handling code checks specifically for 400, you will miss validation failures. Consider handling both status codes, or checking the FastAPI documentation for your version's error format.
The CLI requires a local installation
The CLI surface is a local process, not a remote service call. This means it is not suitable for environments where you cannot install Python packages — for example, a minimal container or a serverless function runtime. In those contexts, the HTTP API is the only viable surface.
No pagination contract documented
GET /orders accepts a limit query parameter, but the source material does not describe a pagination mechanism (cursor, offset, or page number). If your order volume grows beyond a single page, you may have no documented way to retrieve subsequent results. Confirm the pagination strategy before building a production integration against this endpoint.