diff --git a/.github/workflows/publish-types.yaml b/.github/workflows/publish-types.yaml new file mode 100644 index 000000000..3e0b443cc --- /dev/null +++ b/.github/workflows/publish-types.yaml @@ -0,0 +1,71 @@ +name: Publish Python Types + +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + publish-types: + name: Publish Python Types + runs-on: ubuntu-latest + concurrency: publish-types + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.event.release.tag_name }} + + - uses: actions/setup-node@v5 + with: + node-version-file: '.nvmrc' + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + run: npm ci + + - name: Bundle OpenAPI spec + run: npx @redocly/cli bundle docs/openapi/api.yaml -o /tmp/openapi.json --config docs/openapi/redocly-config.yaml + + - name: Generate Python types + run: | + mkdir -p /tmp/types-py/spacecat_api_types + uv tool install datamodel-code-generator + datamodel-codegen \ + --input /tmp/openapi.json \ + --input-file-type openapi \ + --output /tmp/types-py/spacecat_api_types/models.py \ + --output-model-type pydantic_v2.BaseModel \ + --target-python-version 3.11 \ + --field-constraints \ + --use-annotated \ + --snake-case-field + + - name: Prepare package + env: + VERSION: ${{ github.event.release.tag_name }} + run: | + cp clients/python/pyproject.toml /tmp/types-py/ + cp clients/python/README.md /tmp/types-py/ + cp clients/python/spacecat_api_types/__init__.py /tmp/types-py/spacecat_api_types/ + sed -i "s/version = \"0.0.0\"/version = \"${VERSION#v}\"/" /tmp/types-py/pyproject.toml + + - name: Push to types-py branch + tag + run: | + VERSION="${{ github.event.release.tag_name }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout --orphan types-py-tmp + git rm -rf . + cp -r /tmp/types-py/* . + git add -A + git commit -m "types-py ${VERSION}" + git push origin HEAD:types-py --force + git tag "types-py-${VERSION}" + git push origin "types-py-${VERSION}" diff --git a/.gitignore b/.gitignore index bec92764b..3a8482f1f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ admin-idp-p*.json # V1 to V2 migration scripts and test data (local only) scripts/ docs/index.html +docs/openapi/dist/ +clients/python/spacecat_api_types/models.py diff --git a/clients/python/README.md b/clients/python/README.md new file mode 100644 index 000000000..6364864cc --- /dev/null +++ b/clients/python/README.md @@ -0,0 +1,34 @@ +# spacecat-api-types + +Generated Pydantic v2 models for the SpaceCat API service. + +## Installation + +Install from a specific version tag: + +```bash +uv add "spacecat-api-types @ git+https://github.com/adobe/spacecat-api-service.git@types-py-v1.0.0" +``` + +## Usage + +```python +from spacecat_api_types import Site, Organization, V2Brand, LlmoConfig + +# Parse an API response +site = Site.model_validate(response.json()) +print(site.base_url) # snake_case access, alias handles camelCase from API + +# Serialize back to camelCase for API requests +payload = brand_input.model_dump(by_alias=True, exclude_none=True) +``` + +## Field Naming + +Models use **snake_case** Python field names with **camelCase** aliases matching the API: + +- `site.base_url` (Python) ← `baseURL` (API) +- `brand.social_accounts` (Python) ← `socialAccounts` (API) +- `config.ai_topics` (Python) ← `aiTopics` (API) + +`model_validate()` accepts both forms. Use `model_dump(by_alias=True)` to serialize back to camelCase. diff --git a/clients/python/pyproject.toml b/clients/python/pyproject.toml new file mode 100644 index 000000000..dbfe4ef7f --- /dev/null +++ b/clients/python/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "spacecat-api-types" +version = "0.0.0" +description = "Generated Pydantic v2 models for the SpaceCat API" +requires-python = ">=3.11" +dependencies = ["pydantic>=2.0"] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/clients/python/spacecat_api_types/__init__.py b/clients/python/spacecat_api_types/__init__.py new file mode 100644 index 000000000..c382d915b --- /dev/null +++ b/clients/python/spacecat_api_types/__init__.py @@ -0,0 +1 @@ +from .models import * # noqa: F401, F403 diff --git a/package.json b/package.json index a89f05480..6102cca2c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "docs": "npm run docs:lint && npm run docs:build", "docs:build": "npx @redocly/cli build-docs -o ./docs/index.html --config docs/openapi/redocly-config.yaml", "docs:lint": "npx @redocly/cli lint --config docs/openapi/redocly-config.yaml", + "docs:bundle": "npx @redocly/cli bundle docs/openapi/api.yaml -o docs/openapi/dist/openapi.json --config docs/openapi/redocly-config.yaml", "docs:serve": "npx @redocly/cli preview --project-dir docs/openapi --product redoc", "prepare": "husky" },