Skip to content

Commit 1d53734

Browse files
committed
Init
0 parents  commit 1d53734

42 files changed

Lines changed: 3311 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/uv.yml‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: uv
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
strategy:
15+
matrix:
16+
python-version:
17+
- "3.9"
18+
- "3.10"
19+
- "3.11"
20+
- "3.12"
21+
- "3.13"
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@v5
28+
with:
29+
enable-cache: true
30+
31+
- name: Set up Python
32+
run: uv python install ${{ matrix.python-version }}
33+
34+
- name: Install the project
35+
run: |
36+
uv venv --python ${{ matrix.python-version }}
37+
uv pip install -e ".[dev]"
38+
39+
- name: Run tests
40+
run: uv run pytest

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.vscode
2+
.idea
3+
*venv*
4+
__pycache__
5+
*.pyc

‎ARCHITECTURE.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Architecture
2+
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).
4+
5+
## Core Concepts
6+
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.
10+
11+
## Directory Structure
12+
13+
The repository is modularized by domain, ensuring that parsing and emitting logic for specific Python/OpenAPI concepts remain isolated:
14+
15+
```
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+
```
41+
42+
## The Sync Lifecycle (`cdd sync --dir`)
43+
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.

‎COMPLIANCE.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# OpenAPI 3.2.0 Compliance
2+
3+
This project is actively pursuing 100% compliance with the **OpenAPI 3.2.0 Specification**.
4+
5+
## Supported Features
6+
7+
The following OpenAPI 3.2.0 structures are fully modeled in `src/openapi_client/models.py` via Pydantic and actively supported in the AST parsing/emitting pipeline:
8+
9+
### Document Structure
10+
- **OpenAPI Object**: Core root object supporting `openapi: 3.2.0`, `info`, `paths`, `components`, `servers`, `security`, `tags`, `webhooks`, and `$self`.
11+
- **Info Object**: Fully supported, including `Contact` and `License` objects.
12+
- **Server / ServerVariable Objects**: Fully modeled.
13+
- **Paths / PathItem Objects**: Supports standard HTTP methods (`get`, `post`, `put`, `delete`, `patch`, `options`, `head`, `trace`) as well as the new OAS 3.2.0 `query` method and `additionalOperations` mapping.
14+
- **Operation Object**: Supports `operationId`, `summary`, `description`, `parameters`, `requestBody`, `responses`, `callbacks`, `security`, etc.
15+
- **Components Object**: Models schemas, responses, parameters, examples, requestBodies, headers, securitySchemes, links, callbacks, pathItems, and `mediaTypes` (new in OAS 3.2.0).
16+
17+
### Schema & Data Types
18+
- **Schema Object**: Includes full JSON Schema validation properties (`type`, `properties`, `items`, `allOf`, `anyOf`, `oneOf`, `discriminator`, etc.).
19+
- **Reference Object**: Full support for `$ref` pointer objects across paths, components, parameters, etc.
20+
21+
### Media Types & Encodings (OAS 3.2.0 Enhancements)
22+
- **MediaType Object**: Supports the newly defined streaming and sequential payload fields `itemSchema`, `prefixEncoding`, and `itemEncoding`.
23+
- **Encoding Object**: Supports headers, standard encodings, `prefixEncoding`, and `itemEncoding`.
24+
25+
## Partial / Ongoing Support
26+
While the data models strictly validate against OpenAPI 3.2.0 schemas, the *AST manipulation* logic is still incrementally supporting features:
27+
28+
1. **Complex Parameters**: Deep object serializations for Query strings (`spaceDelimited`, `pipeDelimited`) are modeled but code-generation for them currently maps to generic `Any` types in the Python client.
29+
2. **Streaming / Webhooks**: Models exist, but FastAPI mock extraction and test extraction for event-driven / server-sent events (`text/event-stream`) is currently stubbed.
30+
3. **Implicit Connections**: Cross-document `$ref` resolution is currently limited to local `#/...` JSON pointers within the `openapi.json` file.
31+
32+
## Testing Compliance
33+
Compliance is strictly enforced via 100% test coverage and `mypy` strict mode checking across all model definitions and serializers.

‎DEVELOPING.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Developing
2+
3+
Welcome to the `cdd-python-client` development guide. This project is built using Python 3.9+ and relies heavily on AST manipulation using `libcst`.
4+
5+
## Setup
6+
7+
1. **Clone and Setup Virtual Environment:**
8+
```bash
9+
git clone <repository_url>
10+
cd cdd-python-client
11+
python3 -m venv venv
12+
source venv/bin/activate
13+
```
14+
15+
2. **Install Dependencies (Including Dev):**
16+
```bash
17+
pip install -e .[dev]
18+
```
19+
*Note: This will install `pydantic`, `libcst`, `urllib3`, `pytest`, `pytest-cov`, and `mypy`.*
20+
21+
## Running Tests
22+
23+
We maintain **100% test coverage**. Any new features must be fully covered.
24+
25+
```bash
26+
# Run pytest with coverage reporting
27+
PYTHONPATH=src pytest --cov=src --cov-report=term-missing
28+
```
29+
30+
## Type Checking
31+
32+
We maintain **100% Type Safety**. All modules must pass strict mypy validation.
33+
34+
```bash
35+
mypy src/openapi_client
36+
```
37+
38+
## Extending the Architecture
39+
40+
The codebase relies on a pattern of `parse.py` (AST to Spec) and `emit.py` (Spec to AST) inside domain-specific directories (`src/openapi_client/<domain>/`).
41+
42+
### Adding a New Domain
43+
1. **Create the directory**: `mkdir src/openapi_client/new_domain/`
44+
2. **Create `parse.py`**:
45+
- Write a `libcst.CSTVisitor` subclass to traverse Python AST and mutate the shared `OpenAPI` state model.
46+
3. **Create `emit.py`**:
47+
- Write functions that take an `OpenAPI` state model and return `libcst.CSTNode` objects (like `cst.ClassDef` or `cst.FunctionDef`).
48+
4. **Wire into `cli.py`**:
49+
- Make sure your extractor is called inside `sync_dir()` and `extract_from_code()`.
50+
- Make sure your emitter is called when assembling the client module.
51+
52+
### Updating Models
53+
Models are located in `src/openapi_client/models.py`. We use `pydantic`. If you add self-referencing schemas or complex union types, ensure you update the `model_rebuild()` calls at the bottom of the file.

‎PUBLISH.md‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Publishing the CDD Python Client
2+
3+
This document outlines how to publish the `cdd-python-client` library and CLI to Python's official package repository, as well as how to generate and host the project's documentation.
4+
5+
## 1. Publishing to PyPI (Python Package Index)
6+
7+
PyPI is the standard and most popular package registry for Python. Publishing there makes the library installable via `pip install cdd-python-client`.
8+
9+
### Prerequisites
10+
- Create an account on [PyPI](https://pypi.org/) and [TestPyPI](https://test.pypi.org/).
11+
- Generate an API token from your PyPI account settings.
12+
- Install the build and publish tools:
13+
```bash
14+
pip install build twine
15+
```
16+
17+
### Build and Upload
18+
1. **Clean previous builds** (optional but recommended):
19+
```bash
20+
rm -rf dist/ build/
21+
```
22+
2. **Build the wheel and sdist**:
23+
```bash
24+
python -m build
25+
```
26+
*This uses `hatchling` as configured in the `pyproject.toml` to generate files in the `dist/` directory.*
27+
3. **Upload to PyPI**:
28+
```bash
29+
python -m twine upload dist/*
30+
```
31+
*When prompted, use `__token__` as the username and your generated API token as the password.*
32+
33+
---
34+
35+
## 2. Generating Local Documentation (Static Serving)
36+
37+
To generate static HTML documentation directly from the source code and docstrings, we recommend `pdoc` (or `mkdocs`/`sphinx`). `pdoc` requires zero configuration and produces a ready-to-serve static HTML folder.
38+
39+
### Generate HTML
40+
```bash
41+
pip install pdoc
42+
pdoc src/openapi_client -o docs_html
43+
```
44+
45+
### Serve Locally
46+
You can preview the generated static documentation by serving the `docs_html` directory:
47+
```bash
48+
python -m http.server -d docs_html 8000
49+
```
50+
Visit `http://localhost:8000` in your browser. This `docs_html` folder can be uploaded to any static web host (e.g., AWS S3, GitHub Pages, Vercel).
51+
52+
---
53+
54+
## 3. Publishing Documentation to Read the Docs
55+
56+
[Read the Docs (RTD)](https://readthedocs.org/) is the most popular hosting platform for open-source Python documentation.
57+
58+
### Setup Steps
59+
1. **Create a `.readthedocs.yaml` file** in the root of the repository:
60+
```yaml
61+
version: 2
62+
build:
63+
os: ubuntu-22.04
64+
tools:
65+
python: "3.12"
66+
python:
67+
install:
68+
- requirements: requirements.txt
69+
- method: pip
70+
path: .
71+
sphinx:
72+
configuration: docs/conf.py
73+
```
74+
2. **Initialize Sphinx** (if not already done):
75+
```bash
76+
pip install sphinx sphinx-rtd-theme
77+
mkdir docs && cd docs
78+
sphinx-quickstart
79+
```
80+
*Update `docs/conf.py` to use `html_theme = 'sphinx_rtd_theme'` and configure the `sys.path` to point to `../src`.*
81+
3. **Connect to Read the Docs**:
82+
- Log in to [readthedocs.org](https://readthedocs.org/).
83+
- Click **Import a Project**.
84+
- Connect your GitHub repository. RTD will automatically hook into your git pushes and build/host the documentation whenever the `main` branch is updated.

‎PUBLISH_OUTPUT.md‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Publishing & Automating the Generated Client
2+
3+
This document outlines how to publish the **generated API client** (the output of the `cdd sync` command) and how to keep it automatically synchronized with your upstream server's OpenAPI specification.
4+
5+
## 1. Packaging the Generated Client
6+
7+
When `cdd sync` produces a `client.py` and related models, you need to wrap them in a package before distributing. A standard minimal Python package structure looks like this:
8+
9+
```
10+
my-api-client/
11+
├── pyproject.toml
12+
├── README.md
13+
└── src/
14+
└── my_api_client/
15+
├── __init__.py
16+
└── client.py <-- Generated by CDD
17+
```
18+
19+
### Example `pyproject.toml`
20+
Create a `pyproject.toml` in your client repository:
21+
22+
```toml
23+
[build-system]
24+
requires = ["hatchling"]
25+
build-backend = "hatchling.build"
26+
27+
[project]
28+
name = "my-api-client"
29+
version = "1.0.0"
30+
description = "Auto-generated OpenAPI client"
31+
dependencies = [
32+
"pydantic>=2.0",
33+
"urllib3"
34+
]
35+
36+
[tool.hatch.build.targets.wheel]
37+
packages = ["src/my_api_client"]
38+
```
39+
40+
## 2. Publishing the Client to PyPI
41+
42+
To manually publish the generated client to the Python Package Index:
43+
44+
```bash
45+
cd my-api-client
46+
pip install build twine
47+
python -m build
48+
python -m twine upload dist/*
49+
```
50+
51+
---
52+
53+
## 3. Automating Updates via GitHub Actions
54+
55+
The power of CDD is ensuring your client SDK is never out of sync with your API server. You can use a GitHub Actions Cron Job to routinely check your upstream OpenAPI server, sync the models, and publish a new version.
56+
57+
### Setup the GitHub Action Workflow
58+
59+
Create a file in your client repository at `.github/workflows/sync-and-publish.yml`:
60+
61+
```yaml
62+
name: Sync OpenAPI Client
63+
64+
on:
65+
schedule:
66+
# Run every day at midnight UTC
67+
- cron: '0 0 * * *'
68+
workflow_dispatch: # Allow manual trigger via GitHub UI
69+
70+
jobs:
71+
sync_and_publish:
72+
runs-on: ubuntu-latest
73+
steps:
74+
- name: Checkout Client Repository
75+
uses: actions/checkout@v4
76+
77+
- name: Set up Python
78+
uses: actions/setup-python@v5
79+
with:
80+
python-version: '3.12'
81+
82+
- name: Install dependencies
83+
run: |
84+
python -m pip install --upgrade pip
85+
pip install cdd-python-client build twine bumpver
86+
87+
- name: Fetch Latest OpenAPI Spec
88+
# Replace the URL with your server's live openapi.json endpoint
89+
run: curl -s https://api.my-service.com/openapi.json > openapi.json
90+
91+
- name: Sync Client Code
92+
# This updates the `src/my_api_client` files based on the fetched JSON
93+
run: cdd sync --from-openapi openapi.json --to-python src/my_api_client/
94+
95+
- name: Check for changes
96+
id: git-check
97+
run: |
98+
git diff --exit-code || echo "changes=true" >> $GITHUB_OUTPUT
99+
100+
- name: Bump Version and Commit
101+
if: steps.git-check.outputs.changes == 'true'
102+
run: |
103+
git config --global user.name "github-actions[bot]"
104+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
105+
106+
# Automatically bump the patch version in pyproject.toml
107+
bumpver update --patch
108+
109+
git add src/my_api_client/ openapi.json pyproject.toml
110+
git commit -m "chore(openapi): auto-sync client with upstream spec"
111+
git push
112+
113+
- name: Build and Publish to PyPI
114+
if: steps.git-check.outputs.changes == 'true'
115+
env:
116+
TWINE_USERNAME: __token__
117+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
118+
run: |
119+
python -m build
120+
twine upload dist/*
121+
```
122+
123+
### How it Works:
124+
1. **Cron Schedule**: The action runs daily at midnight (or manually).
125+
2. **Fetch**: It pulls the absolute latest `openapi.json` from the upstream server.
126+
3. **CDD Sync**: It runs the `cdd sync` CLI tool to regenerate the client, mock, and test files using the new spec.
127+
4. **Diff Check**: It uses `git diff` to see if the generated AST code differs from what is currently checked in.
128+
5. **Bump & Commit**: If the spec changed, it bumps the version in `pyproject.toml` and commits the code.
129+
6. **Publish**: It builds the new wheel and uploads it to PyPI automatically (requires a `PYPI_API_TOKEN` secret to be set in your GitHub repository settings).

0 commit comments

Comments
 (0)