Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ jobs:
ruff format --check .
- name: Type check
run: mypy --strict src
- name: Check consumed wire types
run: python scripts/generate_wire.py --check
- name: Check breaking schema changes
if: matrix.python-version == '3.12'
run: python scripts/check_wire_drift.py
- name: Test
run: pytest -q --cov=shieldlabs --cov-report=term-missing

Expand Down Expand Up @@ -70,6 +75,12 @@ jobs:
python -m pip install build==1.6.1 twine==7.0.0
python -m build
twine check --strict dist/*
- name: Test installed wheel
run: |
python -m venv /tmp/wheel-consumer
/tmp/wheel-consumer/bin/pip install dist/*.whl
cd /tmp
/tmp/wheel-consumer/bin/python "$GITHUB_WORKSPACE/scripts/smoke_wheel.py"

generated:
name: Generated API types
Expand All @@ -78,7 +89,12 @@ jobs:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.12"
- name: Rebuild generated files
run: ./generate.sh
run: |
python3 -m pip install -e ".[dev]"
./generate.sh
- name: Fail if generated files drifted
run: git diff --exit-code
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ jobs:
ruff check .
ruff format --check .
mypy --strict src
python scripts/generate_wire.py --check
python scripts/check_wire_drift.py
pytest -q
- name: Build
run: |
python -m build
twine check --strict dist/*
- name: Test installed wheel
run: |
python -m venv /tmp/wheel-consumer
/tmp/wheel-consumer/bin/pip install dist/*.whl
cd /tmp
/tmp/wheel-consumer/bin/python "$GITHUB_WORKSPACE/scripts/smoke_wheel.py"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dist
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ dist/

# Virtual environments
.venv/
.wheel-consumer/
.wire-drift-*/
venv/

# Tooling caches and reports
Expand Down
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ All notable changes to this project are documented in this file. The format foll
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.0.1] - 2026-10-05

### Added

- `sync.sh` downloads the OpenAPI description and `generate.sh` rebuilds `generated/` from it. The supported client is unchanged.
- `sync.sh` downloads the OpenAPI description. Schema-derived wire fields now drive History,
profile and webhook normalization, with generated request parameter types and CI checks
for stale output and incompatible schema changes. Public models and tolerant decoding stay
unchanged. The strict reference client in `generated/` remains separate.

## [1.0.0] - 2026-09-30

Expand Down
42 changes: 42 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Run the same checks as CI:

```bash
ruff check . && ruff format --check . && mypy --strict src
python scripts/generate_wire.py --check
python scripts/check_wire_drift.py
pytest -q --cov=shieldlabs --cov-report=term-missing
```

Expand All @@ -30,6 +32,46 @@ pytest -q --cov=shieldlabs --cov-report=term-missing
- The FastAPI example has its own smoke test:
`pip install -r examples/requirements.txt && pytest tests/test_example_app.py`.

## Updating the HTTP contract

`resources/shieldlabs-api.yaml` is the bundled OpenAPI input. Run
`python scripts/generate_wire.py` after updating it. The deterministic output
`src/shieldlabs/_generated_wire.py` is included in the wheel and is read by the real
History/profile/webhook normalizers and request builders. PyYAML and the pinned formatter
are development dependencies only; the installed SDK still depends only on httpx.

The generated fields describe wire types, while the boundary helpers retain missing/null/
malformed-value defaults, unknown strings and raw fields. They do not validate entire
responses or coerce UUIDs/dates. The strict models under `generated/` require extra runtime
dependencies and reject values the supported client accepts, so they remain reference code.
`./generate.sh` regenerates both layers (Docker is needed only for the reference client).

The mutation check regenerates temporary schemas and type-checks copies of the actual SDK
source. Renamed fields, incompatible types, query parameters and headers must be rejected;
optional additive fields and parameters must compile. Unsupported new required parameters
on the consumed HTTP operations fail generation, including inherited path-level parameters.
The profile request path comes from the operation's OpenAPI route; a mutation test verifies
the changed route reaches an actual mocked HTTP request. Ping timestamp and version fields
are checked against the ping model separately from scored events. The checks never edit the
checked-in API description.

History's route template also comes from OpenAPI, with lookup values escaped before template
substitution. The current HTTP operations require GET; changing their method fails generation.
The generated lookup enum must match the real validation list before generation proceeds.
Public and local IP objects have separate generated fields, so one can change without hiding
an incompatible change in the other. Both webhook discriminator definitions are checked
against the supported envelope field and event values before generating.

To check the installed artifact without an editable checkout:

```bash
python -m pip install build
python -m build
python -m venv .wheel-consumer
.wheel-consumer/bin/pip install dist/*.whl
.wheel-consumer/bin/python scripts/smoke_wheel.py
```

## Shared test fixtures

`tests/data/` holds the test fixtures that every ShieldLabs server SDK passes: History API
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,16 @@ Keys and request bodies are never logged. Each request carries

## Development

Refresh the generated client when the API description changes. This does not replace the supported library in this repository.
The supported SDK consumes schema-derived wire fields for History, domain profiles and
webhooks, and generated request parameter types. Its public models, tolerant decoding,
retries and signature verification remain unchanged. The strict reference client under
`generated/` is separate and is not installed as part of the package.

```bash
./sync.sh # download the current OpenAPI description into resources/
./generate.sh # rebuild generated/ from that file
python scripts/generate_wire.py # rebuild the wire types used by the supported SDK
python scripts/generate_wire.py --check # reject stale wire types
./generate.sh # rebuild both wire types and the reference client (requires Docker)
```


Expand Down
3 changes: 3 additions & 0 deletions generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ set -euo pipefail

cd "$(dirname "${BASH_SOURCE[0]}")"

# The supported package consumes these fields, rather than the strict reference transport.
python3 scripts/generate_wire.py

if ! docker info >/dev/null 2>&1; then
echo "Docker is not running. Start Docker and run this script again." >&2
exit 1
Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ dev = [
"pytest>=7.4,<10",
"pytest-cov>=4.1,<8",
"respx>=0.20.2,<0.24",
"ruff>=0.6,<0.17",
"ruff==0.16.10",
"PyYAML==6.0.3",
]

[project.urls]
Expand All @@ -62,7 +63,7 @@ path = "src/shieldlabs/_version.py"
packages = ["src/shieldlabs"]

[tool.hatch.build.targets.sdist]
include = ["src/shieldlabs", "tests", "examples", "README.md", "CHANGELOG.md", "LICENSE"]
include = ["src/shieldlabs", "tests", "examples", "scripts", "resources", "README.md", "CONTRIBUTING.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down Expand Up @@ -98,6 +99,7 @@ keep-runtime-typing = true

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["PT011"]
"src/shieldlabs/_generated_wire.py" = ["N815"]

[tool.mypy]
strict = true
Expand Down
Loading
Loading