You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
BREAKING: The anvil command line tool's dependencies, click and tabulate, are now the cli extra: pip install 'python-anvil[cli]' (#96). Importing python_anvil no longer imports the CLI.
BREAKING: One requests-based transport replaces gql for GraphQL and the ratelimit decorators for REST. Both dependencies are dropped.
Anvil(api_key, base_url=...) sends every request to one host: REST to {base_url}/api/..., GraphQL to {base_url}/graphql. base_url defaults to production. The endpoint_url argument is removed, and environment does nothing: the rate limits now come from the API.
OAuth access tokens are supported: Anvil(access_token=...). JWTs are sent as-is, other tokens base64 encoded, like node-anvil.
Rate limits are learned from the first response that carries the X-RateLimit-Limit and X-RateLimit-Interval-Ms headers. Requests made before the first response arrives wait for it. The client counts requests over a sliding window, like the API, and only waits when the limit has been reached. A request counts from when it is sent until an interval after its response arrives, so the API, which counts from when the request reached it, never sees more in its window than the client allowed. Every client in the process with the same base_url and credentials shares one limiter, and so the limits the API taught it; the starting request_limit only applies to the first of them. A 429 is retried after its Retry-After plus 50ms, or after a second when there is no usable Retry-After, for as long as it takes. The retry option is removed.
Every request, REST included, is retried on 500, 502, 503 and 504, and when it couldn't connect, up to max_retries times (5) with exponential backoff from 100ms, the statuses and count gql's transport retried for GraphQL requests before. Mutations are retried too, as before. A request that was sent but got no complete response, like a read timeout, is not retried, since the API may have processed it; gql retried those too. max_retries=0 turns it off.
API errors raise AnvilRequestError, with the normalized errors (GraphQL errors, REST errors and validation errors with their fields), the status_code, the headers and any partial data. An error is any status outside 2xx, or a 2xx GraphQL response with errors; a non-JSON error, like a 502 from a proxy, keeps the start of its body. The REST methods used to raise a bare Exception; GraphQL methods raised gql's errors. AnvilRequestException and AnvilException remain as aliases, and still accept a plain message, but their base class is now Exception rather than BaseException, so except Exception catches them.
Methods return the data: the PDF or zip bytes for fill_pdf, generate_pdf and download_documents, and the response's data dict for the GraphQL methods. A JSON response to a binary method, like fill_pdf with outputFormat: "url", is returned parsed. The include_headers, debug and retry keyword arguments are removed and now raise TypeError, as do the gql options query and mutate used to pass through; the headers are on AnvilRequestError.
data_type="stream" on the binary methods returns the requests.Response, to stream a large PDF or zip.
Requests time out after 5 minutes by default (timeout). They never did before.
A requests.Session can be passed in (session), e.g. to use a proxy, and user_agent, request_limit and request_limit_ms match node-anvil's options.
BREAKING: request_rest now sends a request, request_rest("POST", "/api/v1/...", json=...), like node-anvil's requestREST. A full URL is accepted if it is on the client's base_url, and refused otherwise. The python_anvil.api_resources.requests module with RestRequest, PlainRequest, GraphqlRequest and FullyQualifiedRequest, and request_fully_qualified, are removed.
BREAKING: Removed with gql: python_anvil.http.GQLClient, get_local_schema and get_gql_ds, Anvil.gql_client, and HTTPClient's old signature and (content, status_code, headers) return. python_anvil.constants loses GRAPHQL_ENDPOINT, REST_ENDPOINT, ANVIL_HOST, VALID_HOSTS, RETRIES_LIMIT, REQUESTS_LIMIT and RATELIMIT_ENV; DEFAULT_BASE_URL is the one host now. The mutation builders' mutation template attribute is gone (pass mutation= to the method instead); mutation_res_query still selects a builder's default response fields.
BREAKING: generate_etch_signing_url returns the URL. get_etch_packet returns the packet, and remove_weld_data whether the data was removed.
BREAKING: query and mutate take the operation as a string; gql documents and its DSL are gone. request_graphql is the same thing with node-anvil's name.
BREAKING: get_casts and get_welds query the API's paginated casts and welds fields, which the old queries no longer matched, and return up to 500 per organization. get_current_user no longer selects each organization's casts.
Add get_etch_packet and remove_weld_data, and response_query and mutation options on create_etch_packet, forge_submit and get_etch_packet, matching node-anvil.
Add Anvil.prepare_graphql_file (also python_anvil.prepare_graphql_file) for multipart uploads of a path, bytes or a file object, anywhere in a mutation's variables. Files given as open file objects used to be base64 encoded into the JSON; they still are unless prepared this way.
The createEtchPacket mutation gains organizationEid and allowUpdates, and the mutations and default response fields now match node-anvil's exactly. forgeSubmit's default fields drop resolvedPayload and weldData.status, and gain continueURL and payloadValue; pass response_query to select others.
forge_submit with a webhook_url works. ForgeSubmitPayload.webhook_url was a pydantic HttpUrl, which the JSON encoder rejected, so it failed before sending; it is a str now.
fill_pdf sends version_number=0 (it used to drop it), and generate_etch_signing_url is also available as generate_etch_sign_url.
Anvil() with no key reads ANVIL_API_KEY from the environment, the way the examples and the CLI do. The client keeps api_key and access_token as attributes.
Every method takes a timeout keyword argument, overriding the client's timeout for that request.
create_etch_packet and forge_submit take the payload as a JSON string too, like fill_pdf and generate_pdf, and their dicts may hold any kind of file (a template reference, Markdown, HTML or an upload) or model instances, with snake_case or camelCase keys. query and mutate take variables as a JSON string. A response_query may be given with or without braces. An in-person signer is accepted. An uploaded open file is sent under its own filename rather than document.pdf.
GeneratePDFPayload extends the generated request type like the other payload models, so page, logo, include_timestamp and output_format are typed. utils.camel_to_snake keeps acronyms whole now (webhookURL -> webhook_url; it gave webhook_u_r_l). The CLI's cast and weld without an eid exit with a usage error (code 2) instead of a traceback. CREATE_ETCH_PACKET and FORGE_SUBMIT are still exported from the builder modules; a BaseQuery subclass with its own mutation template still works.
The client's queries and mutations are checked against the API schema in CI, and make update-schema refreshes the schema from the published @anvilco/types package.
Add test-client.py, which runs every method against a real environment: ANVIL_API_KEY=... make test-client.
Add python_anvil.types: pydantic models generated from the API's OpenAPI spec and GraphQL schema, the Python counterpart of @anvilco/types. types.rest has the REST request bodies, types.graphql every type in the schema, and types.operations the arguments of every query and mutation, like CreateEtchPacketArgs. The client's payload models extend them, so FillPDFPayload, EtchSigner, CreateEtchPacketPayload and ForgeSubmitPayload now accept every option the API has. BREAKING: they also validate those options the way the API documents them: an enum value the API doesn't accept (like signature_mode="wave"), or a number out of its documented range (like token_valid_for_minutes above a day), is a ValidationError before anything is sent, where 5.x passed it through for the API to reject. font_size and routing_order keep accepting any value, as they did. ForgeSubmitPayload.webhook_url is a plain str now, not an HttpUrl.
Add python_anvil.types: pydantic models generated from the API's OpenAPI spec and GraphQL schema, the Python counterpart of @anvilco/types. types.rest has the REST request bodies, types.graphql every type in the schema, and types.operations the arguments of every query and mutation, like CreateEtchPacketArgs. The client's payload models extend them, so FillPDFPayload, EtchSigner, CreateEtchPacketPayload and ForgeSubmitPayload now accept every option the API has, and reject misspelled enum values. ForgeSubmitPayload.webhook_url is a plain str now, not an HttpUrl.
The README now documents every method, option and error, and the docs/ folder and mkdocs are gone. CONTRIBUTING.md covers development, the integration test script, the generated types and publishing.
A Sync API Types workflow refreshes schema/ and regenerates python_anvil/types after each production deploy, opening a PR when anything changed. make update-schema generate does the same by hand.