update to new version of openapi-python-client

now: 0.13.0
This commit is contained in:
TuxCoder 2023-01-13 19:21:38 +01:00
parent deb73d06e6
commit 4a31250bca
159 changed files with 13668 additions and 11420 deletions

View file

@ -61,12 +61,14 @@ client = AuthenticatedClient(
)
```
There are more settings on the generated `Client` class which let you control more runtime behavior, check out the docstring on that class for more info.
Things to know:
1. Every path/method combo becomes a Python module with four functions:
1. `sync`: Blocking request that returns parsed data (if successful) or `None`
1. `sync_detailed`: Blocking request that always returns a `Request`, optionally with `parsed` set if the request was successful.
1. `asyncio`: Like `sync` but the async instead of blocking
1. `asyncio_detailed`: Like `sync_detailed` by async instead of blocking
1. `asyncio`: Like `sync` but async instead of blocking
1. `asyncio_detailed`: Like `sync_detailed` but async instead of blocking
1. All path/query params, and bodies become method arguments.
1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above)

View file

@ -4,13 +4,26 @@ import attr
@attr.s(auto_attribs=True)
class Client:
""" A class for keeping track of data related to the API """
""" A class for keeping track of data related to the API
Attributes:
base_url: The base URL for the API, all requests are made to a relative path to this URL
cookies: A dictionary of cookies to be sent with every request
headers: A dictionary of headers to be sent with every request
timeout: The maximum amount of a time in seconds a request can take. API functions will raise
httpx.TimeoutException if this is exceeded.
verify_ssl: Whether or not to verify the SSL certificate of the API server. This should be True in production,
but can be set to False for testing purposes.
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
status code that was not documented in the source OpenAPI document.
"""
base_url: str
cookies: Dict[str, str] = attr.ib(factory=dict, kw_only=True)
headers: Dict[str, str] = attr.ib(factory=dict, kw_only=True)
timeout: float = attr.ib(5.0, kw_only=True)
verify_ssl: Union[str, bool, ssl.SSLContext] = attr.ib(True, kw_only=True)
raise_on_unexpected_status: bool = attr.ib(False, kw_only=True)
def get_headers(self) -> Dict[str, str]:
""" Get headers to be used in all endpoints """
@ -39,7 +52,10 @@ class AuthenticatedClient(Client):
""" A Client which has been authenticated for use on secured endpoints """
token: str
prefix: str = "Bearer"
auth_header_name: str = "Authorization"
def get_headers(self) -> Dict[str, str]:
""" Get headers to be used in authenticated endpoints """
return {"Authorization": f"Bearer {self.token}", **self.headers}
"""Get headers to be used in authenticated endpoints"""
auth_header_value = f"{self.prefix} {self.token}" if self.prefix else self.token
return {self.auth_header_name: auth_header_value, **self.headers}

View file

@ -83,15 +83,15 @@ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
{{ parameter.to_string() }},
{% endfor %}
*,
{# Proper _client based on whether or not the endpoint requires authentication #}
{# Proper client based on whether or not the endpoint requires authentication #}
{% if endpoint.requires_security %}
_client: AuthenticatedClient,
{% else %}
_client: Client,
{% endif %}
{# Form data if any #}
{% if endpoint.form_body_class %}
form_data: {{ endpoint.form_body_class.name }},
{% if endpoint.form_body %}
form_data: {{ endpoint.form_body.get_type_string() }},
{% endif %}
{# Multipart data if any #}
{% if endpoint.multipart_body %}
@ -120,7 +120,7 @@ json_body: {{ endpoint.json_body.get_type_string() }},
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
_client=_client,
{% if endpoint.form_body_class %}
{% if endpoint.form_body %}
form_data=form_data,
{% endif %}
{% if endpoint.multipart_body %}
@ -159,6 +159,10 @@ Args:
{% endfor %}
{% endif %}
Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
Response[{{ return_string }}]
"""

View file

@ -1,9 +1,11 @@
from http import HTTPStatus
from typing import Any, Dict, List, Optional, Union, cast
import httpx
from ...client import AuthenticatedClient, Client
from ...types import Response, UNSET
from ... import errors
{% for relative in endpoint.relative_imports %}
{{ relative }}
@ -44,7 +46,7 @@ def _get_kwargs(
"headers": headers,
"cookies": cookies,
"timeout": _client.get_timeout(),
{% if endpoint.form_body_class %}
{% if endpoint.form_body %}
"data": form_data.to_dict(),
{% elif endpoint.multipart_body %}
"files": {{ "multipart_" + endpoint.multipart_body.python_name }},
@ -57,32 +59,32 @@ def _get_kwargs(
}
{% if parsed_responses %}
def _parse_response(*, response: httpx.Response) -> Optional[{{ return_string }}]:
def _parse_response(*, client: Client, response: httpx.Response) -> Optional[{{ return_string }}]:
{% for response in endpoint.responses %}
if response.status_code == {{ response.status_code }}:
{% import "property_templates/" + response.prop.template as prop_template %}
if response.status_code == HTTPStatus.{{ response.status_code.name }}:
{% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %}
{% if prop_template.construct %}
{{ prop_template.construct(response.prop, response.source) | indent(8) }}
{% else %}
{{ response.prop.python_name }} = cast({{ response.prop.get_type_string() }}, {{ response.source }})
{% endif %}
return {{ response.prop.python_name }}
{% else %}
return None
{% endif %}
{% endfor %}
return None
{% endif %}
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(f"Unexpected status code: {response.status_code}")
else:
return None
def _build_response(*, response: httpx.Response) -> Response[{{ return_string }}]:
def _build_response(*, client: Client, response: httpx.Response) -> Response[{{ return_string }}]:
return Response(
status_code=response.status_code,
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
{% if parsed_responses %}
parsed=_parse_response(response=response),
{% else %}
parsed=None,
{% endif %}
parsed=_parse_response(client=client, response=response),
)
@ -100,7 +102,7 @@ def sync_detailed(
**kwargs,
)
return _build_response(response=response)
return _build_response(client=_client, response=response)
{% if parsed_responses %}
def sync(
@ -127,7 +129,7 @@ async def asyncio_detailed(
**kwargs
)
return _build_response(response=response)
return _build_response(client=_client, response=response)
{% if parsed_responses %}
async def asyncio(

View file

@ -0,0 +1,7 @@
""" Contains shared errors types that can be raised from API functions """
class UnexpectedStatus(Exception):
""" Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True """
...
__all__ = ["UnexpectedStatus"]

View file

@ -1,4 +1,4 @@
from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO
from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO, TYPE_CHECKING
{% if model.additional_properties %}
from typing import List
@ -16,9 +16,16 @@ from ..types import UNSET, Unset
{{ relative }}
{% endfor %}
{% for lazy_import in model.lazy_imports %}
{% if loop.first %}
if TYPE_CHECKING:
{% endif %}
{{ lazy_import }}
{% endfor %}
{% if model.additional_properties %}
{% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string() %}
{% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string(quoted=not model.additional_properties.is_base_type) %}
{% endif %}
{% set class_name = model.class_info.name %}
@ -85,7 +92,7 @@ field_dict: Dict[str, Any] = {}
{% endif %}
{% if prop_template and prop_template.transform %}
for prop_name, prop in self.additional_properties.items():
{{ prop_template.transform(model.additional_properties, "prop", "field_dict[prop_name]", multipart=multipart) | indent(4) }}
{{ prop_template.transform(model.additional_properties, "prop", "field_dict[prop_name]", multipart=multipart, declare_type=false) | indent(4) }}
{% elif multipart %}
field_dict.update({
key: (None, str(value).encode(), "text/plain")
@ -113,6 +120,9 @@ return field_dict
{% endmacro %}
def to_dict(self) -> Dict[str, Any]:
{% for lazy_import in model.lazy_imports %}
{{ lazy_import }}
{% endfor %}
{{ _to_dict() | indent(8) }}
{% if model.is_multipart_body %}
@ -122,6 +132,9 @@ return field_dict
@classmethod
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
{% for lazy_import in model.lazy_imports %}
{{ lazy_import }}
{% endfor %}
_d = src_dict.copy()
{% for property in model.required_properties + model.optional_properties %}
{% if property.required %}
@ -146,12 +159,18 @@ return field_dict
{% if model.additional_properties %}
{% if model.additional_properties.template %}{# Can be a bool instead of an object #}
{% import "property_templates/" + model.additional_properties.template as prop_template %}
{% if model.additional_properties.lazy_imports %}
{% for lazy_import in model.additional_properties.lazy_imports %}
{{ lazy_import }}
{% endfor %}
{% endif %}
{% else %}
{% set prop_template = None %}
{% endif %}
{% if prop_template and prop_template.construct %}
additional_properties = {}
for prop_name, prop_dict in _d.items():
for prop_name, prop_dict in d.items():
{{ prop_template.construct(model.additional_properties, "prop_dict") | indent(12) }}
additional_properties[prop_name] = {{ model.additional_properties.python_name }}

View file

@ -3,3 +3,11 @@
{% for import in imports | sort %}
{{ import }}
{% endfor %}
{% if imports %}
__all__ = (
{% for all in alls | sort %}
"{{ all }}",
{% endfor %}
)
{% endif %}

View file

@ -1,2 +1,7 @@
""" {{ package_description }} """
from .client import AuthenticatedClient, Client
__all__ = (
"AuthenticatedClient",
"Client",
)

View file

@ -33,3 +33,7 @@ if not isinstance({{ source }}, Unset):
{% endif %}
{% endif %}
{% endmacro %}
{% macro transform_header(property, source, destination) %}
{{ destination }} = str({{ source }})
{% endmacro %}

View file

@ -40,24 +40,24 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri
{% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False) %}
{% if not property.required or property.nullable %}
{{ destination }}{% if declare_type %}: {{ property.get_type_string(json=True) }}{% endif %}
{% set ns = namespace(contains_properties_without_transform = false, contains_modified_properties = not property.required, has_if = false) %}
{% if declare_type %}{{ destination }}: {{ property.get_type_string(json=True) }}{% endif %}
{% if not property.required %}
if isinstance({{ source }}, Unset):
{{ destination }} = UNSET
{% endif %}
{% set ns.has_if = true %}
{% endif %}
{% if property.nullable %}
{% if property.required %}
if {{ source }} is None:
{% else %}{# There's an if UNSET statement before this #}
{% if ns.has_if %}
elif {{ source }} is None:
{% else %}
if {{ source }} is None:
{% set ns.has_if = true %}
{% endif %}
{{ destination }} = None
{% endif %}
{% set ns = namespace(contains_properties_without_transform = false, contains_modified_properties = not property.required) %}
{% for inner_property in property.inner_properties %}
{% import "property_templates/" + inner_property.template as inner_template %}
{% if not inner_template.transform %}
@ -66,8 +66,9 @@ elif {{ source }} is None:
{% else %}
{% set ns.contains_modified_properties = true %}
{% endif %}
{% if loop.first and property.required and not property.nullable %}{# No if UNSET or if None statement before this #}
{% if not ns.has_if %}
if isinstance({{ source }}, {{ inner_property.get_instance_type_string() }}):
{% set ns.has_if = true %}
{% elif not loop.last or ns.contains_properties_without_transform %}
elif isinstance({{ source }}, {{ inner_property.get_instance_type_string() }}):
{% else %}

View file

@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "{{ package_name }}/py.typed"]
[tool.poetry.dependencies]
python = "^3.7"
httpx = ">=0.15.4,<0.23.0"
httpx = ">=0.15.4,<0.24.0"
attrs = ">=21.3.0"
python-dateutil = "^2.8.0"

View file

@ -13,6 +13,6 @@ setup(
long_description_content_type="text/markdown",
packages=find_packages(),
python_requires=">=3.7, <4",
install_requires=["httpx >= 0.15.0, < 0.23.0", "attrs >= 21.3.0", "python-dateutil >= 2.8.0, < 3"],
install_requires=["httpx >= 0.15.0, < 0.24.0", "attrs >= 21.3.0", "python-dateutil >= 2.8.0, < 3"],
package_data={"{{ package_name }}": ["py.typed"]},
)

View file

@ -1,4 +1,5 @@
""" Contains some shared types for properties """
from http import HTTPStatus
from typing import Any, BinaryIO, Generic, MutableMapping, Optional, Tuple, TypeVar
import attr
@ -35,7 +36,7 @@ T = TypeVar("T")
class Response(Generic[T]):
""" A response from an endpoint """
status_code: int
status_code: HTTPStatus
content: bytes
headers: MutableMapping[str, str]
parsed: Optional[T]

File diff suppressed because it is too large Load diff