Migrating to Python generator v6
Version 6.0.0 of the Python SDK generator changes import paths, request configuration, pagination, the underlying HTTP library, and several runtime behaviors. Release the regenerated SDK as a new major version, and share this page with your SDK users so they can update their code.
Before you upgrade
Generator versions 6.0.0 and later read configuration from sdk-config.yml instead of generators.yml. Run fern sdk migrate to create sdk-config.yml from your existing configuration.
Models and errors are imported from .models and .errors
The package root exports only the sync client, the async client, and Environment. Models move to <package>.models and exceptions to <package>.errors. The async client is named <Sdk>Async instead of Async<Sdk>. Other class names are unchanged.
To fix: Update import lines.
request_options becomes request_config
Every method takes a request_config dict instead of a request_options object. Passing request_options= raises TypeError. Inside the dict, timeout_in_seconds becomes timeout and max_retries becomes retry. There’s no replacement for additional_query_parameters, additional_body_parameters, or chunk_size; pass query and body values as method arguments.
To fix: Rename the keyword, pass a dict, and rename the keys.
Pagers no longer expose a page object
Paginated methods still return SyncPager[T] or AsyncPager[T], and iterating a pager still yields items. iter_pages() yields each page’s list of items directly, and the pager has no .items, .has_next, .next_page(), or .response. A pager can be iterated only once. Endpoints that previously returned a plain page model also return a pager.
To fix: Replace manual page loops with pager iteration, and replace page.items with page.
The client uses requests, and two constructor parameters are removed
The SDK uses requests instead of httpx. The httpx_client and headers constructor parameters raise TypeError. Default headers move to set_config. Configure proxies, a custom transport, TLS settings, and connection limits on a requests.Session and pass it as http_client. The follow_redirects constructor parameter works as before, and request logging is configured with the logger constructor parameter, which replaces logging.
To fix: Replace httpx_client with a requests.Session passed as http_client, move default headers to set_config, and rename logging to logger. Keep follow_redirects as is.
Timeouts and connection failures raise SDK errors
A timeout raises ApiTimeoutError. A refused connection, DNS failure, or TLS failure raises ApiConnectionError. Both subclass ApiError, have no status code, and keep the underlying exception on .cause.
To fix: Replace httpx exception handlers with the SDK error types.
Undeclared enum values raise a validation error
Enums are Enum classes validated on input, with no fallback member. Previously, an enum was a string union that accepted unknown values. If the API returns a value the spec doesn’t declare, parsing the response raises ValidationError.
To fix: Keep the spec’s enums in sync with the API. If the API adds enum values without a version change, turn off response validation for the client or for a call:
Array parameters are serialized in their declared style
Array headers, array path parameters, and spaceDelimited or pipeDelimited query parameters are serialized in the style the spec declares. Previously, array headers and path parameters were sent as a Python repr, and delimited query parameters were sent as repeated keys. An empty list is omitted.
To fix: Update any server-side code that parses the previous format.
Retries are configured with a retry dict
Set retries with a retry dict on the constructor, a resource, a method, or a single call. http_methods_to_retry, status_codes_to_retry, and max_delay_ms take effect. Previously, every method was retried on 5xx, 408, 409, and 429. Failed streams are also retried.
To fix: Replace max_retries with retry, and set attempts explicitly.
Multipart uploads can’t set the part filename
File parameters take bytes. The (filename, file, content_type) tuple is no longer accepted. Each part is named after its field and sent as application/octet-stream.
To fix: Pass the file contents as bytes. If the server reads the filename, send it in a field the spec declares.
Server-sent events are parsed frame by frame
If a single SSE frame contains several data: lines, they can be combined into one event, so the consumer receives fewer events than the server sent.
To fix: If your API sends several events in one frame, test a representative stream against the regenerated SDK.
Each security scheme has a constructor parameter
When an API declares several security schemes, every scheme has its own constructor parameter. Previously, some schemes, such as Basic, had no parameter and the header had to be set manually.
To fix: Remove manually built auth headers and pass credentials to the constructor.
Resource clients have their own credential setters
Resource clients expose credential setters, such as set_access_token(), that apply only to that resource client. Previously, using a second identity required a second client.
Set credentials on the constructor for the whole SDK, on a resource client for that resource, or in request_config for a single call.
File downloads return bytes
Binary responses are read in full and returned as bytes instead of an open stream, so large downloads are held in memory.
To fix: Remove the context manager and write the returned bytes.
Each declared status has its own response type
Responses for different declared statuses, such as 200 and 201, are deserialized to their own schemas. A declared 202 or plain string response parses instead of raising. Text and XML responses are decoded as text and XML instead of bytes. ParsingError is removed: a body that doesn’t match its schema raises pydantic’s ValidationError, and status errors are raised from <package>.errors.
To fix: Replace except ParsingError with except ValidationError, and check status_code where an operation declares several success responses.
Text and XML request bodies are sent
A text/plain request body is passed as a request_body argument. Previously, the method had no body parameter. An application/xml body is sent as XML instead of form-encoded.
To fix: Replace manual HTTP calls with the generated method.
Cookie parameters are method arguments
Declared cookie parameters are method arguments. Previously, they were omitted from the generated method.
To fix: Pass cookies as arguments and remove manually built Cookie headers.
OAuth client credentials are handled by the client
Pass client_id and client_secret to the constructor. The client fetches and caches a token on the first request, sends it as a bearer token, and includes endpoint scopes in the token request.
To fix: Remove code that fetches tokens manually.
Configuration can be scoped
The same configuration keys apply at four levels: the constructor, a resource client (set_config()), a method (set_<method>_config()), and a single call (request_config). The narrowest level wins. set_base_url(), set_timeout(), and set_config() also apply to resource clients you’ve already used.
To fix: No changes are required. You can replace multiple clients that differ only in configuration with scoped configuration.
Models are mutable
Models are no longer frozen. Assignments are validated, so an invalid value raises when you set it. Enum fields accept the member or its value.
To fix: Replace model_copy(update=...) workarounds with assignment where appropriate. If code relies on models being immutable, such as using them as dict keys, copy them first.
Raw responses are available on every client and page
with_raw_response returns HttpResponse[T] with .data, .status_code, and .headers, and is also available on the root client. For paginated endpoints, iter_pages_with_response() returns the status and headers of each page. Pass optional parameters to raw methods by keyword.
To fix: Replace pager.response with iter_pages_with_response().
WebSocket channels
The changes above also apply to WebSocket clients: frame models are imported from <package>.models, connect() takes request_config instead of request_options, and frame models are mutable. request_config has no additional_query_parameters equivalent, so pass declared query parameters as connect() arguments. The following changes are specific to WebSockets.
The environment has no socket addresses
Environment is an enum with one URL per member instead of an object with an attribute per host. Each socket host is its own member, so reading a host as an attribute, such as env.events, raises AttributeError. Read .url from the member instead. Two members with the same URL are merged, so one of them reports the other’s .name and doesn’t appear when you iterate the enum.
Passing an environment as base_url also moves every socket channel to that environment’s host. Previously, each channel kept its own host.
To fix: Replace socket attribute reads with .url, check code that logs, stores, or compares Environment.<member>.name, and if you select a non-default environment, set the socket host on connect() with request_config.
The credential isn’t read from an environment variable
The constructor no longer reads the credential from an environment variable or raises when it’s missing. A client with no credentials is created successfully and fails on the first call.
To fix: Read the environment variable and check it where you create the client.
Some connect parameter types change
Connect parameters that accepted any value are annotated as str. Only the annotation changes: the SDK still serializes integers, lists, and dicts as before, so only a type checker reports the difference. Enum parameter types keep their type but gain a Parameter suffix.
To fix: Rename the suffixed enum types, and convert or ignore the scalar values that a type checker flags. Don’t convert a list or dict with str(), because the SDK then sends its string representation as one value.
Control-frame types are merged
Frames that share a definition are generated as one type with a type discriminator. For example, separate keep-alive and close-stream types are removed. The payload on the wire is unchanged. Some receive-only frame types are removed and have no replacement.
To fix: Replace each removed type with the merged type and set type explicitly. Remove handlers for receive events that no longer exist.
Received frames are typed
Iterating a socket yields typed frame models, and recv() returns a model instead of a dict. Previously, the iterator dropped text frames and logged a warning about unknown message types.
To fix: Remove code that parses frames from dicts, and update isinstance(message, dict) checks.