Skip to content

Commit 229f55e

Browse files
author
Oliver Kingston
committed
release: v0.1.1 — drop PhoneResult patches, regen from spec 1.0.3
postio-api 1.0.3 ships an aligned spec and runtime: PhoneResult.isReachable is now boolean|null (matches the HLR return), and the runtime always emits every nullable field as explicit null instead of dropping them on parse failure. The hand-patches that papered over the drift are no longer needed. postio/_models.py is now generated verbatim from @postio/openapi@1.0.3.
1 parent 4fc2826 commit 229f55e

5 files changed

Lines changed: 31 additions & 65 deletions

File tree

CLAUDE.md

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,12 @@ version lives in the response envelope and is independent of SDK
119119
ergonomics changes. Stay on `0.x` until the API contract is committed
120120
stable; bump to `1.0` only with a real public-API freeze.
121121

122-
## Spec drift
122+
## Spec ↔ runtime alignment
123123

124-
`postio/_models.py` is generated, but a couple of fields need manual
125-
patches to reflect runtime reality:
126-
127-
1. **`PhoneResult`** — every nullable field is patched to `= None`
128-
default. The spec marks them `required` with type `[string, null]`,
129-
but on invalid input the live API drops them entirely.
130-
2. **`PhoneResult.isReachable`** — patched to `bool | str | None`. Spec
131-
says string-only; live API returns `bool`.
132-
133-
`scripts/codegen.py` prints a reminder of these patches on every regen.
134-
When postio-api ships a spec/runtime alignment, drop the patches and
135-
let the generator drive `_models.py` verbatim.
124+
As of `@postio/openapi@1.0.3` (postio-api 1.0.3), the generated
125+
`_models.py` is shipped verbatim — no hand-patches required. If a
126+
future spec change re-introduces drift, prefer fixing it at the source
127+
(postio-api Zod schemas + handlers) over patching downstream.
136128

137129
## Secrets the CI needs
138130

postio/_models.py

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
2-
# filename: postio-openapi.json
3-
# timestamp: 2026-05-02T13:39:32+00:00
2+
# filename: openapi.json
3+
# timestamp: 2026-05-02T20:02:36+00:00
44

55
from __future__ import annotations
66

@@ -82,11 +82,11 @@ class Deliverability(Enum):
8282
Aggregated verdict.
8383
"""
8484

85-
deliverable = "deliverable"
86-
undeliverable = "undeliverable"
87-
risky = "risky"
88-
unknown = "unknown"
89-
invalid = "invalid"
85+
deliverable = 'deliverable'
86+
undeliverable = 'undeliverable'
87+
risky = 'risky'
88+
unknown = 'unknown'
89+
invalid = 'invalid'
9090

9191

9292
class EmailResult(BaseModel):
@@ -99,33 +99,26 @@ class EmailResult(BaseModel):
9999
mxFound: bool
100100
smtpCheck: str | None
101101
isCatchAll: bool | None
102-
deliverability: Deliverability = Field(..., description="Aggregated verdict.")
102+
deliverability: Deliverability = Field(..., description='Aggregated verdict.')
103103

104104

105105
class PhoneResult(BaseModel):
106-
# SPEC DRIFT (2026-05-02): spec marks all optional fields as `required`
107-
# with type `[string, null]`, but on invalid input the live API omits
108-
# them entirely (instead of returning null). Until postio-api aligns
109-
# spec with runtime, default every nullable field to None on this model
110-
# so customers don't get a parse error on real responses. Also: the live
111-
# API returns `bool` for `isReachable` even though the spec says string.
112-
# Reapply this block after every codegen.
113106
number: str
114107
isValid: bool
115108
isPossible: bool
116-
type: str | None = None
117-
countryCode: str | None = None
118-
countryName: str | None = None
119-
nationalFormat: str | None = None
120-
internationalFormat: str | None = None
121-
e164Format: str | None = None
122-
originalCarrier: str | None = None
123-
currentCarrier: str | None = None
124-
isPorted: bool | None = None
125-
isReachable: bool | str | None = None
126-
mcc: str | None = None
127-
mnc: str | None = None
128-
level: str | None = None
109+
type: str | None
110+
countryCode: str | None
111+
countryName: str | None
112+
nationalFormat: str | None
113+
internationalFormat: str | None
114+
e164Format: str | None
115+
originalCarrier: str | None
116+
currentCarrier: str | None
117+
isPorted: bool | None
118+
isReachable: bool | None
119+
mcc: str | None
120+
mnc: str | None
121+
level: str | None
129122
lookupError: str | None = None
130123

131124

postio/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.1.0"
1+
__version__ = "0.1.1"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "postio"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
description = "Python SDK for the Postio API — UK address, email, and phone validation. PAF + Ordnance Survey backed. Sync + async, type-safe via Pydantic v2."
55
readme = "README.md"
66
requires-python = ">=3.10"

scripts/codegen.py

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env python3
22
"""Regenerate ``postio/_models.py`` from the live OpenAPI spec.
33
4-
Pulls https://postio.co.uk/openapi.json, runs datamodel-code-generator into
5-
``postio/_models.py``, then prints a reminder about manual patches that need
6-
to be reapplied.
4+
Pulls https://postio.co.uk/openapi.json and runs datamodel-code-generator
5+
into ``postio/_models.py``. As of @postio/openapi@1.0.3 no hand-patches
6+
are needed — the generated model is shipped verbatim.
77
88
Usage:
99
@@ -25,24 +25,6 @@
2525
REPO = Path(__file__).resolve().parent.parent
2626
TARGET = REPO / "postio" / "_models.py"
2727

28-
# Manual patches that must survive every regen. Keep this list in sync with
29-
# the comments in ``_models.py``. Each patch is described as:
30-
# 1. The runtime drift it papers over.
31-
# 2. The exact change to make.
32-
PATCHES_REMINDER = """
33-
After every regen, reapply these manual patches in postio/_models.py:
34-
35-
PhoneResult — spec marks every nullable field as `required` and the API
36-
drops them on invalid input. Add `= None` defaults to every
37-
str | None / bool | None field (number, isValid, isPossible
38-
stay required). Also: change `isReachable: str | None` to
39-
`isReachable: bool | str | None = None` because the live
40-
API returns booleans there.
41-
42-
If postio-api ever ships a spec/runtime alignment, drop the patches and let
43-
the regen drive the model verbatim.
44-
"""
45-
4628

4729
def fetch_spec(dest: Path, source: str) -> None:
4830
if source.startswith(("http://", "https://")):
@@ -90,7 +72,6 @@ def main() -> int:
9072
run_codegen(spec_path, TARGET)
9173

9274
print(f"\nwrote {TARGET}")
93-
print(PATCHES_REMINDER)
9475
return 0
9576

9677

0 commit comments

Comments
 (0)