Generate modern Python clients from OpenAPI
MIT License
Bot releases are hidden (Show)
You can now define a content_type_overrides
field in your config.yml
:
content_type_overrides:
application/zip: application/octet-stream
This allows openapi-python-client
to generate code for content types it doesn't recognize.
PR #1010 closes #810. Thanks @gaarutyunov!
Client
for pyrightThis should resolve incompatibilities between the generated Client
class and the pyright type checker.
PR #1009 closes #909. Thanks @patrick91!
Published by dbanty 8 months ago
Metadata generated for PDM will now use the new distribution = true
syntax instead of package-type = "library"
.
New packages generated with --meta pdm
will require PDM 2.12.0
or later to build.
UnexpectedStatus
exceptionThe error message for UnexpectedStatus
exceptions will now include the UTF-8 decoded (ignoring errors) body of the response.
PR #989 implements #840. Thanks @harabat!
Before now, path parameters which were invalid Python identifiers were not allowed, and would fail generation with an
"Incorrect path templating" error. In particular, this meant that path parameters with hyphens were not allowed.
This has now been fixed!
PR #986 fixed issue #976. Thanks @harabat!
[!WARNING]
This change may break custom templates, see this diff
if you have trouble upgrading.
Published by dbanty 8 months ago
This does not affect projects that are not using --custom-template-path
The type of these properties on Endpoint
has been changed from Dict[str, Property]
to List[Property]
:
path_parameters
query_parameters
header_parameters
cookie_parameters
If your templates are very close to the default templates, you can probably just remove .values()
anywhere it appears.
The type of iter_all_parameters()
is also different, you probably want list_all_parameters()
instead.
This only affects projects using the generate
command, not the update
command. The pyproject.toml
file generated which configures Ruff for linting and formatting has been updated to the 0.2 syntax, which means it will no longer work with Ruff 0.1.
While fixing #922, some naming strategies were updated. These should mostly be backwards compatible, but there may be
some small differences in generated code. Make sure to check your diffs before pushing updates to consumers!
If you have two parameters to an endpoint named mixedCase
and mixed_case
, previously, this was a conflict and the endpoint would not be generated.
Now, the generator will skip snake-casing the parameters and use the names as-is. Note that this means if neither of the parameters was snake case, neither will be in the generated code.
Fixes #922 reported by @macmoritz & @benedikt-bartscher.
If you had an object with two properties, where the names differed only by case, conflicting properties would be generated in the model, which then failed the linting step (when using default config). For example, this:
type: "object"
properties:
MixedCase:
type: "string"
mixedCase:
type: "string"
Would generate a class like this:
class MyModel:
mixed_case: str
mixed_case: str
Now, neither of the properties will be forced into snake case, and the generated code will look like this:
class MyModel:
MixedCase: str
mixedCase: str
Published by dbanty 8 months ago
Nested union types (unions of unions) were generating isinstance()
checks that were not valid (at least for Python 3.9).
Thanks to @codebutler for PR #959 which fixes #958 and #967.
Published by dbanty 9 months ago
--meta=pdm
option for generating PEP621 + PDM metadataThe default metadata is still --meta=poetry
, which generates a pyproject.toml
file with Poetry-specific metadata.
This change adds the --meta=pdm
option which includes PDM-specific metadata, but also
standard PEP621
metadata. This may be useful as a starting point for other dependency managers & build tools (like Hatch).
data
attribute to Response
objectPR #767
In custom templates, you can now access a response.data
attribute that contains the original OpenAPI definition of the
response (Response Object or Reference Object).
UP
rule for generated Ruff configThis enables pyupgrade-like improvements which should replace some
.format()
calls with f-strings.
--meta=none
PR #940 fixes issue #939. Thanks @satwell!
Due to the lack of pyproject.toml
, Ruff was not getting configured properly when --meta=none
.
As a result, it didn't clean up common generation issues like duplicate imports, which would then cause errors from
linters.
This is now fixed by changing the default post_hook
to ruff check . --fix --extend-select=I
when --meta=none
.
Using generate --meta=none
should now be almost identical to the code generated by update
.
Published by dbanty 10 months ago
Unset
types from generated types.py
(#927)If a schema has both type = "boolean"
and enum
defined, a normal boolean property will now be created.
Previously, the generator would error.
Note that the generate code will not correctly limit the values to the enum values. To work around this, use the
OpenAPI 3.1 const
instead of enum
to generate Python Literal
types.
Thanks for reporting #922 @macmoritz!
This generator only supports enum
values that are strings or integers.
Previously, this was handled at the parsing level, which would cause the generator to fail if there were any unsupported values in the document.
Now, the generator will correctly keep going, skipping only endpoints which contained unsupported values.
Thanks for reporting #922 @macmoritz!
Fixes #756 and #928. Arrays within unions (which, as of 0.17 includes nullable arrays) would generate invalid code.
Thanks @kgutwin and @diesieben07!
Published by dbanty 10 months ago
In previous versions, setting either nullable: true
or required: false
on a query parameter would act like both were set, resulting in a type signature like Union[None, Unset, YourType]
. This special case has been removed, query parameters will now act like all other types of parameters.
PR #900 addresses #822.
Where previously there would be one body parameter per supported content type, now there is a single body
parameter which takes a union of all the possible inputs. This correctly models the fact that only one body can be sent (and ever would be sent) in a request.
For example, when calling a generated endpoint, code which used to look like this:
post_body_multipart.sync_detailed(
client=client,
multipart_data=PostBodyMultipartMultipartData(),
)
Will now look like this:
post_body_multipart.sync_detailed(
client=client,
body=PostBodyMultipartBody(),
)
Note that both the input parameter name and the class name have changed. This should result in simpler code when there is only a single body type and now produces correct code when there are multiple body types.
The generator will now attempt to generate code for OpenAPI documents with versions 3.1.x (previously, it would exit immediately on seeing a version other than 3.0.x). The following specific OpenAPI 3.1 features are now supported:
null
as a typetype: [string, null]
)const
(defines Literal
types)The generator does not currently validate that the OpenAPI document is valid for a specific version of OpenAPI, so it may be possible to generate code for documents that include both removed 3.0 syntax (e.g., nullable
) and new 3.1 syntax (e.g., null
as a type).
Thanks to everyone who helped make this possible with discussions and testing, including:
requestBody
PR #900 addresses #822.
It is now possible in some circumstances to generate valid code for OpenAPI documents which have multiple possible requestBody
values. Previously, invalid code could have been generated with no warning (only one body could actually be sent).
Only one content type per "category" is currently supported at a time. The categories are:
application/json
application/octet-stream
application/x-www-form-urlencoded
multipart/form-data
In previous versions, a request body that was similar to a known content type would use that content type in the request. For example application/json
would be used for application/vnd.api+json
. This was incorrect and could result in invalid requests being sent.
Now, the content type defined in the OpenAPI document will always be used.
Published by dbanty 10 months ago
Published by dbanty 11 months ago
black
is no longer a runtime dependency, so if you have them set in custom post_hooks
in a config file, you'll need to make sure they're being installed manually. ruff
is now installed and used by default instead.
isort
and autoflake
are no longer runtime dependencies, so if you have them set in custom post_hooks
in a config file, you'll need to make sure they're being installed manually. ruff
is now installed and used by default instead.
text/*
content types in responsesWithin an API response, any content type which starts with text/
will now be treated the same as text/html
already was—they will return the response.text
attribute from the httpx Response.
Thanks to @fdintino for the initial implementation, and thanks for the discussions from @kairntech, @rubenfiszel, and @antoneladestito.
Closes #797 and #821.
application/octet-stream
request bodiesEndpoints that accept application/octet-stream
request bodies are now supported using the same File
type as octet-stream responses.
Thanks to @kgutwin for the implementation and @rtaycher for the discussion!
PR #899 closes #588
pass
statements from generated codePublished by dbanty about 1 year ago
Published by dbanty about 1 year ago
Published by dbanty about 1 year ago
Some features of generated clients already failed at runtime when using httpx < 0.20, but now the minimum version is enforced at generation time.
Client
and AuthenticatedClient
now reuse an internal httpx.Client
(or AsyncClient
)—keeping connections open between requests. This will improve performance overall, but may cause resource leaking if clients are not closed properly. The new clients are intended to be used via context managers—though for compatibility they don't have to be used with context managers. If not using a context manager, connections will probably leak. Note that once a client is closed (by leaving the context manager), it can no longer be used—and attempting to do so will raise an exception.
APIs should now be called like:
with client as client:
my_api.sync(client)
another_api.sync(client)
# client is closed here and can no longer be used
Generated READMEs reflect the new syntax, but READMEs for existing generated clients should be updated manually. See this diff for inspiration.
@define
and field
APIsSee the attrs docs for more information on how these may affect you.
Client
and AuthenticatedClient
The following attributes have been removed from Client
and AuthenticatedClient
:
base_url
—this can now only be set via the initializercookies
—set at initialization or use .with_cookies()
headers
—set at initialization or use .with_headers()
timeout
—set at initialization or use .with_timeout()
verify_ssl
—this can now only be set via the initializerfollow_redirects
—this can now only be set via the initializertimeout
param and with_timeout
now take an httpx.Timeout
instead of a floatAuthenticatedClient
no longer inherits from Client
The API of AuthenticatedClient
is still a superset of Client
, but the two classes no longer share a common base class.
httpx
clientsThere are many use-cases where customizing the underlying httpx
client directly is necessary. Some examples are:
The new Client
and AuthenticatedClient
classes come with several methods to customize underlying clients. You can pass arbitrary arguments to httpx.Client
or httpx.AsyncClient
when they are constructed:
client = Client(base_url="https://api.example.com", httpx_args={"proxies": {"https://": "https://proxy.example.com"}})
The underlying clients are constructed lazily, only when needed. httpx_args
are stored internally in a dictionary until the first request is made.
You can force immediate construction of an underlying client in order to edit it directly:
import httpx
from my_api import Client
client = Client(base_url="https://api.example.com")
sync_client: httpx.Client = client.get_httpx_client()
sync_client.timeout = 10
async_client = client.get_async_httpx_client()
async_client.timeout = 15
You can also completely override the underlying clients:
import httpx
from my_api import Client
client = Client(base_url="https://api.example.com")
# The params you put in here ^ are discarded when you call set_httpx_client or set_async_httpx_client
sync_client = httpx.Client(base_url="https://api.example.com", timeout=10)
client.set_httpx_client(sync_client)
async_client = httpx.AsyncClient(base_url="https://api.example.com", timeout=15)
client.set_async_httpx_client(async_client)
This happens every time you use the same Client
or AuthenticatedClient
instance for multiple requests, however it is best to use a context manager (e.g., with client as client:
) to ensure the client is closed properly.
Published by dbanty over 1 year ago
Published by dbanty over 1 year ago
Unset
(e.g., using if statements to check type) [#714, #752]. Thanks @taasan & @mcclurem!Published by dbanty over 1 year ago
Published by dbanty over 1 year ago
Published by dbanty over 1 year ago
required
field in parameters included with $ref
(#737). Thanks @robertschweizer!Published by dbanty almost 2 years ago
http_timeout
config to set timeout getting document via --url
[#718]. Thanks @Kircheneer!Published by dbanty almost 2 years ago
post_hooks
in package directory instead of current directory if meta=none [#696, #697]. Thanks @brenmous and @wallagib!Client.get_headers
function [#713]. Thanks @rtaycher!Published by dbanty almost 2 years ago
raise_on_unexpected_status
flag to generated Client
[#593]. Thanks @JamesHinshelwood, @ramnes, @gwenshap, @theFong!use_path_prefixes_for_title_model_names
config option for simpler model names [#559, #560]. Thanks @rtaycher!+json
[#706, #709]. Thanks @XioNoX and @mtovt!