Tier 0 Product1
Architecture

Architecture

Service architecture, components, and data flow


Overview

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.


Architecture diagram
pycli — System Architecture Components and data flow Developer / User pycli Click application deploy --region --dry-run [env] config set --value [key] FastAPI api:app /orders POST · GET CreateOrderBody (Pydantic) /orders/{"{order_id}"} GET · DELETE Pydantic Schema JSON body validation CLI HTTP / curl Stack / Target us-east-1 · staging · prod deploys to Config Store key / value pairs config set Control / data flow HTTP request Side effect / external Primary component Schema / Store

Components

pycli exposes two independent surfaces that share no runtime coupling. Each component below plays a distinct role in the system.

ComponentTypeRole
pycli CLIClick applicationThe command-line entry point. Accepts structured flags and arguments to trigger deployment and configuration operations on your behalf.
deploy commandCLI subcommandExecutes 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 subcommandCLI subcommandWrites 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 APIFastAPI 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 /ordersAPI endpointCreates a new order. Accepts a JSON body validated against the CreateOrderBody Pydantic schema, ensuring your payload is well-formed before processing.
GET /ordersAPI endpointReturns 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 endpointRetrieves 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 endpointCancels 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 CreateOrderBodySchema / validation layerDefines 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.

Data flow

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

  1. You send an HTTP POST request to https://api.example.com/orders with a JSON body containing the fields required by CreateOrderBody (for example, amount and currency).

  2. The FastAPI application (api:app) receives the request. FastAPI routes the request to the POST /orders handler based on the method and path.

  3. Pydantic validates the request body. Before your handler logic runs, FastAPI passes the raw JSON through the CreateOrderBody schema. If any required field is missing or the wrong type, Pydantic rejects the request immediately and returns a 422 Unprocessable Entity response with details about the validation failure. Your application code never runs in this case.

  4. The handler processes the validated order and returns a response — typically the created order object with an assigned order_id.

  5. You receive the response and can use the returned order_id in subsequent calls, such as GET /orders/{order_id} to poll status or DELETE /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.


Design decisions

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.


Trade-offs

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.