Repository navigation
[Feature] Modernization: pydantic types generated from the OpenAPI spec and GraphQL schema - #99
Merged
Merged
Conversation
4 tasks
fostimus
force-pushed
the
df/modernize-generated-types
branch
from
October 21, 2026 22:18
a95dd54 to
a21feed
Compare
fostimus
force-pushed
the
df/modernize-generated-types
branch
7 times, most recently
from
October 28, 2026 17:52
6e392d6 to
94b5c87
Compare
fostimus
force-pushed
the
df/modernize-generated-types
branch
2 times, most recently
from
October 28, 2026 20:13
59612b7 to
8cd0be9
Compare
…documented The docstrings said a dict payload's keys may be snake_case or camelCase, but the builders passed the keys straight to their constructors, so a camelCase key raised. The keys are converted now, with a camel_to_snake that keeps acronyms whole: webhookURL -> webhook_url, mergePDFs -> merge_pdfs. The old one gave webhook_u_r_l. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
python_anvil.types is the Python counterpart of @anvilco/types: - types/rest.py: the REST request bodies, from schema/openapi.json - types/graphql.py: every type in the GraphQL schema, from schema/anvil_schema.graphql - types/operations.py: the arguments of every query and mutation, like CreateEtchPacketArgs, generated from the same schema with graphql-core dev/fetch_schema.py refreshes both specs (the schema from the published @anvilco/types package, so the clients stay in step, and the OpenAPI spec from production; neither needs a key), and dev/generate_types.py regenerates the types offline. CI fails if the types are out of date with the specs. The hand-written payload models now extend the generated ones, so FillPDFPayload, EtchSigner, CreateEtchPacketPayload and ForgeSubmitPayload accept every option the API has and check enum values, instead of drifting from the API by hand. The Sync API Types workflow is anvil-js's: it runs after a production deploy (and weekly as a backstop), refreshes the specs, regenerates, and opens a PR when anything changed. It needs the CI_GITHUB_TOKEN secret, and python-anvil added to Foundry's --webhook-repos list for the dispatch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An audit of the generated types against the transport layer found one regression: `EtchSigner.token_valid_until` had become a pydantic AwareDatetime, which turned the string a caller passed into a datetime the JSON encoder can't send, and rejected a date with no timezone. It is a plain string, like emails already were. - The import rewrite that makes a pydantic type a plain `str` handles a wrapped import too. It only matched a one-line import, so the first sync that added a few pydantic types would have produced a module that tried to import `str` from pydantic. - `font_size` and `routing_order` keep accepting any value, as in 5.x; the other documented ranges and enums the generated models now check are in the changelog as a breaking change. - datamodel-code-generator is pinned to a minor, since its output is byte-compared in CI. Files are read and written as UTF-8, the spec fetches time out, the sync job has a timeout, and its changelog note goes under Unreleased once per release rather than once ever. - A generated field starting with `model_` doesn't warn. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fostimus
force-pushed
the
df/modernize-generated-types
branch
from
October 28, 2026 20:16
8cd0be9 to
a669e6a
Compare
…asier A compatibility audit against 5.1.2's docs, examples and tests found every request body identical, and these gaps, all closed: - `query` and `mutate` take `timeout`, like `request_graphql`, and `variables` as a JSON string, which the 5.x CLI sent. - An empty dict payload raises TypeError as it did; the builder's message says `name` is required rather than naming an optional field. - A `BaseQuery` subclass with its own `mutation` template, the 5.x extension point, still works; CREATE_ETCH_PACKET and FORGE_SUBMIT are still exported; the client keeps `api_key` and `access_token`. And what the audit found tedious, with every existing call unchanged: - `Anvil()` reads ANVIL_API_KEY, the way every example and the CLI do. - Every method takes `timeout`. - `create_etch_packet` and `forge_submit` take a JSON string, as the PDF methods do; their dicts may hold any kind of file (template reference, Markdown, HTML, upload) or model instances, where only an upload dict worked; an in-person signer is accepted; `response_query` may be given without braces; an uploaded open file is sent under its own name. - `GeneratePDFPayload` extends the generated request like the other payload models, so page, logo and output options are typed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fostimus
marked this pull request as ready for review
October 28, 2026 22:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Layer 3 of 4, on master now that #97 and #98 have merged.
python_anvil.typesis the Python counterpart of@anvilco/types: pydantic models generated from the API's OpenAPI spec and GraphQL schema, so the request types stop drifting from the API by hand.types/rest.py: the REST request bodies, fromschema/openapi.json.types/graphql.py: every type in the GraphQL schema, fromschema/anvil_schema.graphql.types/operations.py: the arguments of every query and mutation, likeCreateEtchPacketArgs, generated from the same schema with graphql-core.dev/fetch_schema.pyrefreshes both specs (the schema from the published@anvilco/typespackage, so the two clients stay in step, and the OpenAPI spec from production; neither needs a key).dev/generate_types.pyregenerates offline, and CI fails if the generated files are out of date withschema/.FillPDFPayload,EtchSigner,CreateEtchPacketPayloadandForgeSubmitPayloadnow accept every option the API has and reject misspelled enum values, with the same dumps as before.create_etch_packetandforge_submitdict payloads accept camelCase keys, as their docstrings always said; the builders raised on them. Thecamel_to_snakehelper now keeps acronyms whole (webhookURL->webhook_url,mergePDFs->merge_pdfs).The workflow needs a
CI_GITHUB_TOKENsecret, andpython-anviladded to Foundry's--webhook-reposlist in anvil-infra for the deploy dispatch; until then only the weekly and manual runs happen. Both are in CONTRIBUTING.md.An audit of this layer against #98 found one regression, fixed in the last commit: a signer's
token_valid_untilhad become a pydantic datetime, which the JSON encoder can't send and which rejected a date with no timezone. It is a plain string again. The same commit hardens the generator (the import rewrite handles a wrapped import, files are UTF-8, fetches and the sync job time out, the generator is pinned to a minor since CI byte-compares its output) and keepsfont_sizeandrouting_orderas lenient as 5.x.A final compatibility audit ran every call pattern from 5.1.2's docs, examples and tests against the stack: every request body was identical. The gaps it found are closed in the last commit, and the friction it found is removed without changing any existing call:
Anvil()readsANVIL_API_KEY; every method takestimeout;query/mutatetaketimeoutand JSON-string variables;create_etch_packetandforge_submittake a JSON string and dicts holding any kind of file or model instances; an in-person signer works;response_querymay omit its braces; an uploaded open file keeps its filename;GeneratePDFPayloadis typed like the other payloads; and the 5.x extension points (BaseQuery.mutation,CREATE_ETCH_PACKET,FORGE_SUBMIT,client.api_key) still exist.How to verify
ANVIL_API_KEYset,Anvil()works; without it, the error says to set itanvil.fill_pdf(..., timeout=600)andanvil.query(q, '{"eid": "x"}', timeout=10)workanvil.create_etch_packet({"name": ..., "files": [{"id": "tpl", "castEid": ...}, EtchCastRef(...)], "signers": [EtchSigner(...)]})sends all of them;anvil.create_etch_packet('{"name": ...}')workscreate_etch_packet(packet, response_query="eid status")sends{ eid status }DocumentUpload(file=open("nda.pdf", "rb"))is sent asnda.pdf, notdocument.pdfBaseQuerysubclass withmutation = "... {query} ..."andmutation_res_querystill sends throughmutateEtchSigner(..., tokenValidUntil="2030-01-01T00:00:00Z")dumps the string unchanged andjson.dumpsaccepts it; a date with no timezone is accepted tooFillPDFPayload(data=..., font_size=36)andEtchSigner(..., routing_order=0)are accepted, as in 5.xcreate_etch_packet({"name": ..., "webhookURL": ..., "mergePDFs": True})andforge_submit({"forgeEid": ..., "payload": {}})accept the camelCase keysmake generateis a no-op on this branch (make check-generatedpasses)FillPDFPayload(data=..., use_interactive_fields=True)dumpsuseInteractiveFieldsand does not sendoutputFormatunless setEtchSigner(..., signature_mode="wave")is aValidationError;"draw"is acceptedCreateEtchPacketPayloadis aCreateEtchPacketArgsand dumpsallowUpdatesandmergePDFsunder those namescreateEtchPacket,forgeSubmit,generateEtchSignURL,removeWeldDataandetchPacketoperations is a field of the matching*Argsmodelcreate_etch_packetandforge_submittests still produce the same variables as before this layerBreaking Changes
The payload models validate options the way the API documents them: an enum value the API doesn't accept, or a number outside its documented range (other than
font_sizeandrouting_order), is aValidationErrorbefore anything is sent, where 5.x passed it through for the API to reject. Listed in the changelog. (ForgeSubmitPayload.webhook_urlbecoming astrmoved down to #98.)