Skip to content

Commit ec13111

Browse files
committed
Add licenses ; more consistent text in markdown ; [.github/workflows/uv.yml] Restrict Python version range
1 parent 286b914 commit ec13111

6 files changed

Lines changed: 375 additions & 72 deletions

File tree

.github/workflows/uv.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ jobs:
1414
strategy:
1515
matrix:
1616
python-version:
17-
- "3.9"
18-
- "3.10"
1917
- "3.11"
2018
- "3.12"
2119
- "3.13"

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
__pycache__
55
*.pyc
66
coverage.json
7+
3.2.0.md
8+
.coverage

ARCHITECTURE.md

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,94 @@
1-
# Architecture
1+
# cdd-python-client Architecture
22

3-
The `cdd-python-client` is designed around a strictly modular, bidirectional Code-to-Spec and Spec-to-Code architecture, heavily utilizing Abstract Syntax Trees (AST).
3+
<!-- BADGES_START -->
4+
<!-- Replace these placeholders with your repository-specific badges -->
5+
[![License](https://img.shields.io/badge/license-Apache--2.0%20OR%20MIT-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6+
[![CI/CD](https://github.com/offscale/cdd-python-client/workflows/CI/badge.svg)](https://github.com/offscale/cdd-python-client/actions)
7+
[![Coverage](https://codecov.io/gh/offscale/cdd-python-client/branch/master/graph/badge.svg)](https://codecov.io/gh/offscale/cdd-python-client)
8+
<!-- BADGES_END -->
49

5-
## Core Concepts
10+
The **cdd-python-client** tool acts as a dedicated compiler and transpiler. Its fundamental architecture follows standard compiler design principles, divided into three distinct phases: **Frontend (Parsing)**, **Intermediate Representation (IR)**, and **Backend (Emitting)**.
611

7-
1. **Contract-Driven Development (CDD)**: The source of truth can be dynamically negotiated. You can start with an OpenAPI specification (`openapi.json`) or you can start with Python code (FastAPI mocks, Pydantic models, or pytest tests).
8-
2. **Abstract Syntax Trees (AST)**: Instead of regex or simple templating, this tool utilizes `libcst` (Concrete Syntax Tree) to read and write Python code. This preserves comments, whitespace, and structural integrity.
9-
3. **Pydantic Models**: The OpenAPI 3.2.0 specification is modeled heavily using Pydantic (`models.py`). This guarantees in-memory type safety and validation when parsing or emitting specifications.
12+
This decoupled design ensures that any format capable of being parsed into the IR can subsequently be emitted into any supported output format, whether that is a server-side route, a client-side SDK, a database ORM, or an OpenAPI specification.
1013

11-
## Directory Structure
14+
## 🏗 High-Level Overview
1215

13-
The repository is modularized by domain, ensuring that parsing and emitting logic for specific Python/OpenAPI concepts remain isolated:
16+
```mermaid
17+
graph TD
18+
%% Styling Definitions
19+
classDef frontend fill:#57caff,stroke:#4285f4,stroke-width:2px,color:#20344b,font-family:Roboto Mono
20+
classDef core fill:#ffd427,stroke:#f9ab00,stroke-width:3px,color:#20344b,font-family:Google Sans,font-weight:bold
21+
classDef backend fill:#5cdb6d,stroke:#34a853,stroke-width:2px,color:#20344b,font-family:Roboto Mono
22+
classDef endpoint fill:#ffffff,stroke:#20344b,stroke-width:1px,color:#20344b,font-family:Google Sans
1423
24+
subgraph Frontend [Parsers]
25+
A[OpenAPI .yaml/.json]:::endpoint --> P1(OpenAPI Parser):::frontend
26+
B[`Python` Models / Source]:::endpoint --> P2(`Python` Parser):::frontend
27+
C[Server Routes / Frameworks]:::endpoint --> P3(Framework Parser):::frontend
28+
D[Client SDKs / ORMs]:::endpoint --> P4(Ext Parser):::frontend
29+
end
30+
31+
subgraph Core [Intermediate Representation]
32+
IR((CDD IR)):::core
33+
end
34+
35+
subgraph Backend [Emitters]
36+
E1(OpenAPI Emitter):::backend --> X[OpenAPI .yaml/.json]:::endpoint
37+
E2(`Python` Emitter):::backend --> Y[`Python` Models / Structs]:::endpoint
38+
E3(Server Emitter):::backend --> Z[Server Routes / Controllers]:::endpoint
39+
E4(Client Emitter):::backend --> W[Client SDKs / API Calls]:::endpoint
40+
E5(Data Emitter):::backend --> V[ORM Models / CLI Parsers]:::endpoint
41+
end
42+
43+
P1 --> IR
44+
P2 --> IR
45+
P3 --> IR
46+
P4 --> IR
47+
48+
IR --> E1
49+
IR --> E2
50+
IR --> E3
51+
IR --> E4
52+
IR --> E5
1553
```
16-
src/openapi_client/
17-
├── models.py # Single Source of Truth for OpenAPI 3.2.0 specs
18-
├── cli.py # CLI entrypoints and unified sync workflows
19-
├── openapi/ # JSON/Dict Serialization
20-
│ ├── parse.py
21-
│ └── emit.py
22-
├── classes/ # Pydantic Models <-> OpenAPI Components/Schemas
23-
│ ├── parse.py # Extracts JSON schemas from Python classes
24-
│ └── emit.py # Emits Pydantic models from JSON schemas
25-
├── functions/ # Python Methods <-> OpenAPI Operations
26-
│ ├── parse.py
27-
│ └── emit.py
28-
├── docstrings/ # Python Docstrings <-> OpenAPI Summary/Description
29-
│ ├── parse.py
30-
│ └── emit.py
31-
├── routes/ # Client generation (combining classes + functions)
32-
│ ├── parse.py
33-
│ └── emit.py
34-
├── mocks/ # FastAPI Mocks <-> OpenAPI Paths/Operations
35-
│ ├── parse.py
36-
│ └── emit.py
37-
└── tests/ # Pytest functions <-> OpenAPI Examples/Validation
38-
├── parse.py
39-
└── emit.py
40-
```
4154

42-
## The Sync Lifecycle (`cdd sync --dir`)
55+
## 🧩 Core Components
56+
57+
### 1. The Frontend (Parsers)
58+
59+
The Frontend's responsibility is to read an input source and translate it into the universal CDD Intermediate Representation (IR).
60+
61+
* **Static Analysis (AST-Driven)**: For `Python` source code, the tool **does not** use dynamic reflection or execute the code. Instead, it reads the source files, generates an Abstract Syntax Tree (AST), and navigates the tree to extract classes, structs, functions, type signatures, API client definitions, server routes (like FastAPI mocks), and docstrings.
62+
* **OpenAPI Parsing**: For OpenAPI and JSON Schema inputs, the parser normalizes the structure, resolving internal `$ref`s and extracting properties, endpoints (client or server perspectives), and metadata into the IR.
63+
64+
### 2. Intermediate Representation (IR)
65+
66+
The Intermediate Representation is the crucial "glue" of the architecture. It is a normalized, language-agnostic data structure that represents concepts like:
67+
* **Models**: Entities containing typed properties, required fields, defaults, and descriptions.
68+
* **Endpoints / Operations**: HTTP verbs, paths, path/query/body parameters, and responses. In the IR, an operation is an abstract concept that can represent *either* a Server Route receiving a request *or* an API Client dispatching a request.
69+
* **Metadata**: Tooling hints, docstrings, and validations.
70+
71+
By standardizing on a single IR (heavily inspired by OpenAPI / JSON Schema primitives), the system guarantees that parsing logic and emitting logic remain completely decoupled.
72+
73+
### 3. The Backend (Emitters)
74+
75+
The Backend's responsibility is to take the universal IR and generate valid target output. Emitters can be written to support various environments (e.g., Client vs Server, Web vs CLI).
76+
77+
* **Code Generation**: Emitters iterate over the IR and generate idiomatic `Python` source code.
78+
* A **Server Emitter** creates routing controllers and request-validation logic (for example, generating local test-mock servers).
79+
* A **Client Emitter** creates API wrappers, fetch functions, and response-parsing logic.
80+
* **Database & CLI Generation**: Emitters can also target ORM models or command-line parsers by mapping IR properties to database columns or CLI arguments.
81+
* **Specification Generation**: Emitters translating back to OpenAPI serialize the IR into standard OpenAPI 3.x JSON or YAML, rigorously formatting descriptions, type constraints, and endpoint schemas based on what was parsed from the source code.
82+
83+
## 🔄 Extensibility
84+
85+
Because of the IR-centric design, adding support for a new `Python` framework (e.g., a new Client library, Web framework, or ORM) requires minimal effort:
86+
1. **To support parsing a new framework**: Write a parser that converts the framework's AST/DSL into the CDD IR. Once written, the framework can automatically be exported to OpenAPI, Client SDKs, CLI parsers, or any other existing output target.
87+
2. **To support emitting a new framework**: Write an emitter that converts the CDD IR into the framework's DSL/AST. Once written, the framework can automatically be generated from OpenAPI or any other supported input.
88+
89+
## 🛡 Design Principles
4390

44-
When using the unified sync workflow, the system performs a bidirectional merge:
45-
1. **Parse**: `libcst` Visitors traverse `client.py`, `mock_server.py`, and `test_client.py`. Any discovered routes, Pydantic models, and docstrings are extracted and merged into the in-memory OpenAPI Pydantic model.
46-
2. **Merge**: If an `openapi.json` exists, it is parsed and used as the baseline. Code-extracted definitions overlay or expand the JSON definitions.
47-
3. **Emit**: The fully reconciled OpenAPI model is serialized back to `openapi.json`. Then, `libcst` generators rebuild `client.py`, `mock_server.py`, and `test_client.py`, ensuring 100% synchronization across the client, backend mocks, tests, and documentation.
91+
1. **A Single Source of Truth**: Developers should be able to maintain their definitions in whichever format is most ergonomic for their team (OpenAPI files, Native Code, Client libraries, ORM models) and generate the rest.
92+
2. **Zero-Execution Parsing**: Ensure security and resilience by strictly statically analyzing inputs. The compiler must never need to run the target code to understand its structure.
93+
3. **Lossless Conversion**: Maximize the retention of metadata (e.g., type annotations, docstrings, default values, validators) during the transition `Source -> IR -> Target`.
94+
4. **Symmetric Operations**: An Endpoint in the IR holds all the information necessary to generate both the Server-side controller that fulfills it, and the Client-side SDK method that calls it.

0 commit comments

Comments
 (0)