change python client generator for hydra

This commit is contained in:
TuxCoder 2022-02-19 10:22:21 +01:00
parent c15be406ea
commit 4e9fd55093
128 changed files with 16275 additions and 11 deletions

View file

@ -0,0 +1,23 @@
__pycache__/
build/
dist/
*.egg-info/
.pytest_cache/
# pyenv
.python-version
# Environments
.env
.venv
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# JetBrains
.idea/
/coverage.xml
/.coverage

View file

@ -0,0 +1,87 @@
# {{ project_name }}
{{ package_description }}
## Usage
First, create a client:
```python
from {{ package_name }} import Client
client = Client(base_url="https://api.example.com")
```
If the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead:
```python
from {{ package_name }} import AuthenticatedClient
client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
```
Now call your endpoint and use your models:
```python
from {{ package_name }}.models import MyDataModel
from {{ package_name }}.api.my_tag import get_my_data_model
from {{ package_name }}.types import Response
my_data: MyDataModel = get_my_data_model.sync(client=client)
# or if you need more info (e.g. status_code)
response: Response[MyDataModel] = get_my_data_model.sync_detailed(client=client)
```
Or do the same thing with an async version:
```python
from {{ package_name }}.models import MyDataModel
from {{ package_name }}.api.my_tag import get_my_data_model
from {{ package_name }}.types import Response
my_data: MyDataModel = await get_my_data_model.asyncio(client=client)
response: Response[MyDataModel] = await get_my_data_model.asyncio_detailed(client=client)
```
By default, when you're calling an HTTPS API it will attempt to verify that SSL is working correctly. Using certificate verification is highly recommended most of the time, but sometimes you may need to authenticate to a server (especially an internal server) using a custom certificate bundle.
```python
client = AuthenticatedClient(
base_url="https://internal_api.example.com",
token="SuperSecretToken",
verify_ssl="/path/to/certificate_bundle.pem",
)
```
You can also disable certificate validation altogether, but beware that **this is a security risk**.
```python
client = AuthenticatedClient(
base_url="https://internal_api.example.com",
token="SuperSecretToken",
verify_ssl=False
)
```
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. 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)
1. Any endpoint which did not have a tag will be in `{{ package_name }}.api.default`
## Building / publishing this Client
This project uses [Poetry](https://python-poetry.org/) to manage dependencies and packaging. Here are the basics:
1. Update the metadata in pyproject.toml (e.g. authors, version)
1. If you're using a private repository, configure it with Poetry
1. `poetry config repositories.<your-repository-name> <url-to-your-repository>`
1. `poetry config http-basic.<your-repository-name> <username> <password>`
1. Publish the client with `poetry publish --build -r <your-repository-name>` or, if for public PyPI, just `poetry publish --build`
If you want to install this client into another project without publishing it (e.g. for development) then:
1. If that project **is using Poetry**, you can simply do `poetry add <path-to-this-client>` from that project
1. If that project is not using Poetry:
1. Build a wheel with `poetry build -f wheel`
1. Install that wheel from the other project `pip install <path-to-wheel>`

View file

@ -0,0 +1 @@
""" Contains methods for accessing the API """

View file

@ -0,0 +1,45 @@
import ssl
from typing import Dict, Union
import attr
@attr.s(auto_attribs=True)
class Client:
""" A class for keeping track of data related to the API """
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)
def get_headers(self) -> Dict[str, str]:
""" Get headers to be used in all endpoints """
return {**self.headers}
def with_headers(self, headers: Dict[str, str]) -> "Client":
""" Get a new client matching this one with additional headers """
return attr.evolve(self, headers={**self.headers, **headers})
def get_cookies(self) -> Dict[str, str]:
return {**self.cookies}
def with_cookies(self, cookies: Dict[str, str]) -> "Client":
""" Get a new client matching this one with additional cookies """
return attr.evolve(self, cookies={**self.cookies, **cookies})
def get_timeout(self) -> float:
return self.timeout
def with_timeout(self, timeout: float) -> "Client":
""" Get a new client matching this one with a new timeout (in seconds) """
return attr.evolve(self, timeout=timeout)
@attr.s(auto_attribs=True)
class AuthenticatedClient(Client):
""" A Client which has been authenticated for use on secured endpoints """
token: str
def get_headers(self) -> Dict[str, str]:
""" Get headers to be used in authenticated endpoints """
return {"Authorization": f"Bearer {self.token}", **self.headers}

View file

@ -0,0 +1,165 @@
{% from "property_templates/helpers.jinja" import guarded_statement %}
{% macro header_params(endpoint) %}
{% if endpoint.header_parameters %}
{% for parameter in endpoint.header_parameters.values() %}
{% set destination = 'headers["' + parameter.name + '"]' %}
{% import "property_templates/" + parameter.template as param_template %}
{% if param_template.transform_header %}
{% set statement = param_template.transform_header(parameter, parameter.python_name, destination) %}
{% else %}
{% set statement = destination + " = " + parameter.python_name %}
{% endif %}
{{ guarded_statement(parameter, parameter.python_name, statement) }}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro cookie_params(endpoint) %}
{% if endpoint.cookie_parameters %}
{% for parameter in endpoint.cookie_parameters.values() %}
{% if parameter.required %}
cookies["{{ parameter.name}}"] = {{ parameter.python_name }}
{% else %}
if {{ parameter.python_name }} is not UNSET:
cookies["{{ parameter.name}}"] = {{ parameter.python_name }}
{% endif %}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro query_params(endpoint) %}
{% if endpoint.query_parameters %}
params: Dict[str, Any] = {}
{% for property in endpoint.query_parameters.values() %}
{% set destination = property.python_name %}
{% import "property_templates/" + property.template as prop_template %}
{% if prop_template.transform %}
{% set destination = "json_" + property.python_name %}
{{ prop_template.transform(property, property.python_name, destination) }}
{% endif %}
{%- if not property.json_is_dict %}
params["{{ property.name }}"] = {{ destination }}
{% else %}
{{ guarded_statement(property, destination, "params.update(" + destination + ")") }}
{% endif %}
{% endfor %}
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
{% endif %}
{% endmacro %}
{% macro json_body(endpoint) %}
{% if endpoint.json_body %}
{% set property = endpoint.json_body %}
{% set destination = "json_" + property.python_name %}
{% import "property_templates/" + property.template as prop_template %}
{% if prop_template.transform %}
{{ prop_template.transform(property, property.python_name, destination) }}
{% else %}
{{ destination }} = {{ property.python_name }}
{% endif %}
{% endif %}
{% endmacro %}
{% macro multipart_body(endpoint) %}
{% if endpoint.multipart_body %}
{% set property = endpoint.multipart_body %}
{% set destination = "multipart_" + property.python_name %}
{% import "property_templates/" + property.template as prop_template %}
{% if prop_template.transform_multipart %}
{{ prop_template.transform_multipart(property, property.python_name, destination) }}
{% endif %}
{% endif %}
{% endmacro %}
{# The all the kwargs passed into an endpoint (and variants thereof)) #}
{% macro arguments(endpoint) %}
{# path parameters #}
{% for parameter in endpoint.path_parameters.values() %}
{{ parameter.to_string() }},
{% endfor %}
*,
{# 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 }},
{% endif %}
{# Multipart data if any #}
{% if endpoint.multipart_body %}
multipart_data: {{ endpoint.multipart_body.get_type_string() }},
{% endif %}
{# JSON body if any #}
{% if endpoint.json_body %}
json_body: {{ endpoint.json_body.get_type_string() }},
{% endif %}
{# query parameters #}
{% for parameter in endpoint.query_parameters.values() %}
{{ parameter.to_string() }},
{% endfor %}
{% for parameter in endpoint.header_parameters.values() %}
{{ parameter.to_string() }},
{% endfor %}
{# cookie parameters #}
{% for parameter in endpoint.cookie_parameters.values() %}
{{ parameter.to_string() }},
{% endfor %}
{% endmacro %}
{# Just lists all kwargs to endpoints as name=name for passing to other functions #}
{% macro kwargs(endpoint) %}
{% for parameter in endpoint.path_parameters.values() %}
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
_client=_client,
{% if endpoint.form_body_class %}
form_data=form_data,
{% endif %}
{% if endpoint.multipart_body %}
multipart_data=multipart_data,
{% endif %}
{% if endpoint.json_body %}
json_body=json_body,
{% endif %}
{% for parameter in endpoint.query_parameters.values() %}
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
{% for parameter in endpoint.header_parameters.values() %}
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
{% for parameter in endpoint.cookie_parameters.values() %}
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
{% endmacro %}
{% macro docstring(endpoint, return_string) %}
"""{% if endpoint.summary %}{{ endpoint.summary | wordwrap(100)}}
{% endif -%}
{%- if endpoint.description %} {{ endpoint.description | wordwrap(100) }}
{% endif %}
{% if not endpoint.summary and not endpoint.description %}
{# Leave extra space so that Args or Returns isn't at the top #}
{% endif %}
{% set all_parameters = endpoint.list_all_parameters() %}
{% if all_parameters %}
Args:
{% for parameter in all_parameters %}
{{ parameter.to_docstring() | wordwrap(90) | indent(8) }}
{% endfor %}
{% endif %}
Returns:
Response[{{ return_string }}]
"""
{% endmacro %}

View file

@ -0,0 +1,142 @@
from typing import Any, Dict, List, Optional, Union, cast
import httpx
from ...client import AuthenticatedClient, Client
from ...types import Response, UNSET
{% for relative in endpoint.relative_imports %}
{{ relative }}
{% endfor %}
{% from "endpoint_macros.py.jinja" import header_params, cookie_params, query_params, json_body, multipart_body,
arguments, client, kwargs, parse_response, docstring %}
{% set return_string = endpoint.response_type() %}
{% set parsed_responses = (endpoint.responses | length > 0) and return_string != "Any" %}
def _get_kwargs(
{{ arguments(endpoint) | indent(4) }}
) -> Dict[str, Any]:
url = "{}{{ endpoint.path }}".format(
_client.base_url
{%- for parameter in endpoint.path_parameters.values() -%}
,{{parameter.name}}={{parameter.python_name}}
{%- endfor -%}
)
headers: Dict[str, str] = _client.get_headers()
cookies: Dict[str, Any] = _client.get_cookies()
{{ header_params(endpoint) | indent(4) }}
{{ cookie_params(endpoint) | indent(4) }}
{{ query_params(endpoint) | indent(4) }}
{{ json_body(endpoint) | indent(4) }}
{{ multipart_body(endpoint) | indent(4) }}
return {
"method": "{{ endpoint.method }}",
"url": url,
"headers": headers,
"cookies": cookies,
"timeout": _client.get_timeout(),
{% if endpoint.form_body_class %}
"data": form_data.to_dict(),
{% elif endpoint.multipart_body %}
"files": {{ "multipart_" + endpoint.multipart_body.python_name }},
{% elif endpoint.json_body %}
"json": {{ "json_" + endpoint.json_body.python_name }},
{% endif %}
{% if endpoint.query_parameters %}
"params": params,
{% endif %}
}
{% if parsed_responses %}
def _parse_response(*, 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 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 }}
{% endfor %}
return None
{% endif %}
def _build_response(*, response: httpx.Response) -> Response[{{ return_string }}]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
{% if parsed_responses %}
parsed=_parse_response(response=response),
{% else %}
parsed=None,
{% endif %}
)
def sync_detailed(
{{ arguments(endpoint) | indent(4) }}
) -> Response[{{ return_string }}]:
{{ docstring(endpoint, return_string) | indent(4) }}
kwargs = _get_kwargs(
{{ kwargs(endpoint) }}
)
response = httpx.request(
verify=_client.verify_ssl,
**kwargs,
)
return _build_response(response=response)
{% if parsed_responses %}
def sync(
{{ arguments(endpoint) | indent(4) }}
) -> Optional[{{ return_string }}]:
{{ docstring(endpoint, return_string) | indent(4) }}
return sync_detailed(
{{ kwargs(endpoint) }}
).parsed
{% endif %}
async def asyncio_detailed(
{{ arguments(endpoint) | indent(4) }}
) -> Response[{{ return_string }}]:
{{ docstring(endpoint, return_string) | indent(4) }}
kwargs = _get_kwargs(
{{ kwargs(endpoint) }}
)
async with httpx.AsyncClient(verify=_client.verify_ssl) as __client:
response = await __client.request(
**kwargs
)
return _build_response(response=response)
{% if parsed_responses %}
async def asyncio(
{{ arguments(endpoint) | indent(4) }}
) -> Optional[{{ return_string }}]:
{{ docstring(endpoint, return_string) | indent(4) }}
return (await asyncio_detailed(
{{ kwargs(endpoint) }}
)).parsed
{% endif %}

View file

@ -0,0 +1,9 @@
from enum import IntEnum
class {{ enum.class_info.name }}(IntEnum):
{% for key, value in enum.values.items() %}
{{ key }} = {{ value }}
{% endfor %}
def __str__(self) -> str:
return str(self.value)

View file

@ -0,0 +1,181 @@
from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO
{% if model.additional_properties %}
from typing import List
{% endif %}
import attr
{% if model.is_multipart_body %}
import json
{% endif %}
from ..types import UNSET, Unset
{% for relative in model.relative_imports %}
{{ relative }}
{% endfor %}
{% if model.additional_properties %}
{% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string() %}
{% endif %}
{% set class_name = model.class_info.name %}
{% set module_name = model.class_info.module_name %}
T = TypeVar("T", bound="{{ class_name }}")
@attr.s(auto_attribs=True)
class {{ class_name }}:
"""{% if model.title %}{{ model.title | wordwrap(116) }}
{% endif -%}
{%- if model.description %}{{ model.description | wordwrap(116) }}
{% endif %}
{% if not model.title and not model.description %}
{# Leave extra space so that a section doesn't start on the first line #}
{% endif %}
{% if model.example %}
Example:
{{ model.example | string | wordwrap(112) | indent(12) }}
{% endif %}
{% if model.required_properties or model.optional_properties %}
Attributes:
{% for property in model.required_properties + model.optional_properties %}
{{ property.to_docstring() | wordwrap(112) | indent(12) }}
{% endfor %}{% endif %}
"""
{% for property in model.required_properties + model.optional_properties %}
{% if property.default is none and property.required %}
{{ property.to_string() }}
{% endif %}
{% endfor %}
{% for property in model.required_properties + model.optional_properties %}
{% if property.default is not none or not property.required %}
{{ property.to_string() }}
{% endif %}
{% endfor %}
{% if model.additional_properties %}
additional_properties: Dict[str, {{ additional_property_type }}] = attr.ib(init=False, factory=dict)
{% endif %}
{% macro _to_dict(multipart=False) %}
{% for property in model.required_properties + model.optional_properties %}
{% import "property_templates/" + property.template as prop_template %}
{% if prop_template.transform %}
{{ prop_template.transform(property, "self." + property.python_name, property.python_name, multipart=multipart) }}
{% elif multipart %}
{{ property.python_name }} = self.{{ property.python_name }} if isinstance(self.{{ property.python_name }}, Unset) else (None, str(self.{{ property.python_name }}).encode(), "text/plain")
{% else %}
{{ property.python_name }} = self.{{ property.python_name }}
{% endif %}
{% endfor %}
field_dict: Dict[str, Any] = {}
{% 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 %}
{% else %}
{% set prop_template = None %}
{% 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) }}
{% elif multipart %}
field_dict.update({
key: (None, str(value).encode(), "text/plain")
for key, value in self.additional_properties.items()
})
{% else %}
field_dict.update(self.additional_properties)
{% endif %}
{% endif %}
field_dict.update({
{% for property in model.required_properties + model.optional_properties %}
{% if property.required %}
"{{ property.name }}": {{ property.python_name }},
{% endif %}
{% endfor %}
})
{% for property in model.optional_properties %}
{% if not property.required %}
if {{ property.python_name }} is not UNSET:
field_dict["{{ property.name }}"] = {{ property.python_name }}
{% endif %}
{% endfor %}
return field_dict
{% endmacro %}
def to_dict(self) -> Dict[str, Any]:
{{ _to_dict() | indent(8) }}
{% if model.is_multipart_body %}
def to_multipart(self) -> Dict[str, Any]:
{{ _to_dict(multipart=True) | indent(8) }}
{% endif %}
@classmethod
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
_d = src_dict.copy()
{% for property in model.required_properties + model.optional_properties %}
{% if property.required %}
{% set property_source = '_d.pop("' + property.name + '")' %}
{% else %}
{% set property_source = '_d.pop("' + property.name + '", UNSET)' %}
{% endif %}
{% import "property_templates/" + property.template as prop_template %}
{% if prop_template.construct %}
{{ prop_template.construct(property, property_source) | indent(8) }}
{% else %}
{{ property.python_name }} = {{ property_source }}
{% endif %}
{% endfor %}
{{ module_name }} = cls(
{% for property in model.required_properties + model.optional_properties %}
{{ property.python_name }}={{ property.python_name }},
{% endfor %}
)
{% 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 %}
{% else %}
{% set prop_template = None %}
{% endif %}
{% if prop_template and prop_template.construct %}
additional_properties = {}
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 }}
{{ module_name }}.additional_properties = additional_properties
{% else %}
{{ module_name }}.additional_properties = _d
{% endif %}
{% endif %}
return {{ module_name }}
{% if model.additional_properties %}
@property
def additional_keys(self) -> List[str]:
return list(self.additional_properties.keys())
def __getitem__(self, key: str) -> {{ additional_property_type }}:
return self.additional_properties[key]
def __setitem__(self, key: str, value: {{ additional_property_type }}) -> None:
self.additional_properties[key] = value
def __delitem__(self, key: str) -> None:
del self.additional_properties[key]
def __contains__(self, key: str) -> bool:
return key in self.additional_properties
{% endif %}

View file

@ -0,0 +1,5 @@
""" Contains all the data models used in inputs/outputs """
{% for import in imports | sort %}
{{ import }}
{% endfor %}

View file

@ -0,0 +1,2 @@
""" {{ package_description }} """
from .client import AuthenticatedClient, Client

View file

@ -0,0 +1,3 @@
{% macro transform_header(property, source, destination) %}
{{ destination }} = "true" if {{ source }} else "false"
{% endmacro %}

View file

@ -0,0 +1,35 @@
{% macro construct_function(property, source) %}
isoparse({{ source }}).date()
{% endmacro %}
{% from "property_templates/property_macros.py.jinja" import construct_template %}
{% macro construct(property, source, initial_value=None) %}
{{ construct_template(construct_function, property, source, initial_value=initial_value) }}
{% endmacro %}
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, str){% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False) %}
{% set transformed = source + ".isoformat()" %}
{% if multipart %}{# Multipart data must be bytes, not str #}
{% set transformed = transformed + ".encode()" %}
{% endif %}
{% if property.required %}
{{ destination }} = {{ transformed }} {% if property.nullable %}if {{ source }} else None {%endif%}
{% else %}
{% if declare_type %}
{% set type_annotation = property.get_type_string(json=True) %}
{% if multipart %}{% set type_annotation = type_annotation | replace("str", "bytes") %}{% endif %}
{{ destination }}: {{ type_annotation }} = UNSET
{% else %}
{{ destination }} = UNSET
{% endif %}
if not isinstance({{ source }}, Unset):
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,39 @@
{% macro construct_function(property, source) %}
isoparse({{ source }})
{% endmacro %}
{% from "property_templates/property_macros.py.jinja" import construct_template %}
{% macro construct(property, source, initial_value=None) %}
{{ construct_template(construct_function, property, source, initial_value=initial_value) }}
{% endmacro %}
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, str){% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False) %}
{% set transformed = source + ".isoformat()" %}
{% if multipart %}{# Multipart data must be bytes, not str #}
{% set transformed = transformed + ".encode()" %}
{% endif %}
{% if property.required %}
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% else %}
{% if declare_type %}
{% set type_annotation = property.get_type_string(json=True) %}
{% if multipart %}{% set type_annotation = type_annotation | replace("str", "bytes") %}{% endif %}
{{ destination }}: {{ type_annotation }} = UNSET
{% else %}
{{ destination }} = UNSET
{% endif %}
if not isinstance({{ source }}, Unset):
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,35 @@
{% macro construct_function(property, source) %}
{{ property.class_info.name }}({{ source }})
{% endmacro %}
{% from "property_templates/property_macros.py.jinja" import construct_template %}
{% macro construct(property, source, initial_value=None) %}
{{ construct_template(construct_function, property, source, initial_value=initial_value) }}
{% endmacro %}
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, {{ property.value_type.__name__ }}){% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False) %}
{% set transformed = source + ".value" %}
{% set type_string = property.get_type_string(json=True) %}
{% if multipart %}
{% set transformed = "(None, str(" + transformed + ").encode(), \"text/plain\")" %}
{% set type_string = "Union[Unset, Tuple[None, bytes, str]]" %}
{% endif %}
{% if property.required %}
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% else %}
{{ destination }}{% if declare_type %}: {{ type_string }}{% endif %} = UNSET
if not isinstance({{ source }}, Unset):
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,31 @@
{% macro construct_function(property, source) %}
File(
payload = BytesIO({{ source }})
)
{% endmacro %}
{% from "property_templates/property_macros.py.jinja" import construct_template %}
{% macro construct(property, source, initial_value=None) %}
{{ construct_template(construct_function, property, source, initial_value=initial_value) }}
{% endmacro %}
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, bytes){% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False) %}
{% if property.required %}
{% if property.nullable %}
{{ destination }} = {{ source }}.to_tuple() if {{ source }} else None
{% else %}
{{ destination }} = {{ source }}.to_tuple()
{% endif %}
{% else %}
{{ destination }}{% if declare_type %}: {{ property.get_type_string(json=True) }}{% endif %} = UNSET
if not isinstance({{ source }}, Unset):
{% if property.nullable %}
{{ destination }} = {{ source }}.to_tuple() if {{ source }} else None
{% else %}
{{ destination }} = {{ source }}.to_tuple()
{% endif %}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,3 @@
{% macro transform_header(property, source, destination) %}
{{ destination }} = str({{ source }})
{% endmacro %}

View file

@ -0,0 +1,18 @@
{% macro guarded_statement(property, source, statement) %}
{# If the property can be UNSET or None, this macro returns the provided statement guarded by an if which will check
for those invalid values. Otherwise, it returns the statement unmodified. #}
{% if property.required and not property.nullable %}
{{ statement }}
{% else %}
{% if property.nullable and not property.required %}
if not isinstance({{ source }}, Unset) and {{ source }} is not None:
{{ statement }}
{% elif property.nullable %}
if {{ source }} is not None:
{{ statement }}
{% else %}
if not isinstance({{ source }}, Unset):
{{ statement }}
{% endif %}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,3 @@
{% macro transform_header(property, source, destination) %}
{{ destination }} = str({{ source }})
{% endmacro %}

View file

@ -0,0 +1,77 @@
{% macro construct(property, source, initial_value="[]") %}
{% set inner_property = property.inner_property %}
{% import "property_templates/" + inner_property.template as inner_template %}
{% if inner_template.construct %}
{% set inner_source = inner_property.python_name + "_data" %}
{{ property.python_name }} = {{ initial_value }}
_{{ property.python_name }} = {{ source }}
{% if property.required and not property.nullable %}
for {{ inner_source }} in (_{{ property.python_name }}):
{% else %}
for {{ inner_source }} in (_{{ property.python_name }} or []):
{% endif %}
{{ inner_template.construct(inner_property, inner_source) | indent(4) }}
{{ property.python_name }}.append({{ inner_property.python_name }})
{% else %}
{{ property.python_name }} = cast({{ property.get_type_string(no_optional=True) }}, {{ source }})
{% endif %}
{% endmacro %}
{% macro _transform(property, source, destination, multipart, transform_method) %}
{% set inner_property = property.inner_property %}
{% if multipart %}
{% set multipart_destination = destination %}
{% set destination = "_temp_" + destination %}
{% endif %}
{% import "property_templates/" + inner_property.template as inner_template %}
{% if inner_template.transform %}
{% set inner_source = inner_property.python_name + "_data" %}
{{ destination }} = []
for {{ inner_source }} in {{ source }}:
{{ inner_template.transform(inner_property, inner_source, inner_property.python_name, transform_method) | indent(4) }}
{{ destination }}.append({{ inner_property.python_name }})
{% else %}
{{ destination }} = {{ source }}
{% endif %}
{% if multipart %}
{{ multipart_destination }} = (None, json.dumps({{ destination }}).encode(), 'application/json')
{% endif %}
{% endmacro %}
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, list){% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False, transform_method="to_dict") %}
{% set inner_property = property.inner_property %}
{% if multipart %}
{% set type_string = "Union[Unset, Tuple[None, bytes, str]]" %}
{% else %}
{% set type_string = property.get_type_string(json=True) %}
{% endif %}
{% if property.required %}
{% if property.nullable %}
if {{ source }} is None:
{{ destination }} = None
else:
{{ _transform(property, source, destination, multipart, transform_method) | indent(4) }}
{% else %}
{{ _transform(property, source, destination, multipart, transform_method) }}
{% endif %}
{% else %}
{{ destination }}{% if declare_type %}: {{ type_string }}{% endif %} = UNSET
if not isinstance({{ source }}, Unset):
{% if property.nullable %}
if {{ source }} is None:
{{ destination }} = None
else:
{{ _transform(property, source, destination, multipart, transform_method) | indent(8)}}
{% else %}
{{ _transform(property, source, destination, multipart, transform_method) | indent(4)}}
{% endif %}
{% endif %}
{% endmacro %}
{% macro transform_multipart(property, source, destination) %}
{{ transform(property, source, destination, transform_method="to_multipart") }}
{% endmacro %}

View file

@ -0,0 +1,40 @@
{% macro construct_function(property, source) %}
{{ property.class_info.name }}.from_dict({{ source }})
{% endmacro %}
{% from "property_templates/property_macros.py.jinja" import construct_template %}
{% macro construct(property, source, initial_value=None) %}
{{ construct_template(construct_function, property, source, initial_value=initial_value) }}
{% endmacro %}
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, dict){% endmacro %}
{% macro transform(property, source, destination, declare_type=True, multipart=False, transform_method="to_dict") %}
{% set transformed = source + "." + transform_method + "()" %}
{% if multipart %}
{% set transformed = "(None, json.dumps(" + transformed + ").encode(), 'application/json')" %}
{% set type_string = "Union[Unset, Tuple[None, bytes, str]]" %}
{% else %}
{% set type_string = property.get_type_string(json=True) %}
{% endif %}
{% if property.required %}
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% else %}
{{ destination }}{% if declare_type %}: {{ type_string }}{% endif %} = UNSET
if not isinstance({{ source }}, Unset):
{% if property.nullable %}
{{ destination }} = {{ transformed }} if {{ source }} else None
{% else %}
{{ destination }} = {{ transformed }}
{% endif %}
{% endif %}
{% endmacro %}
{% macro transform_multipart(property, source, destination) %}
{{ transform(property, source, destination, transform_method="to_multipart") }}
{% endmacro %}

View file

@ -0,0 +1,20 @@
{% macro construct_template(construct_function, property, source, initial_value=None) %}
{% if property.required and not property.nullable %}
{{ property.python_name }} = {{ construct_function(property, source) }}
{% else %}{# Must be nullable OR non-required #}
_{{ property.python_name }} = {{ source }}
{{ property.python_name }}: {{ property.get_type_string() }}
{% if property.nullable %}
if _{{ property.python_name }} is None:
{{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}None{% endif %}
{% endif %}
{% if not property.required %}
{% if property.nullable %}elif{% else %}if{% endif %} isinstance(_{{ property.python_name }}, Unset):
{{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}UNSET{% endif %}
{% endif %}
else:
{{ property.python_name }} = {{ construct_function(property, "_" + property.python_name) }}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,85 @@
{% macro construct(property, source, initial_value=None) %}
def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_string() }}:
{% if "None" in property.get_type_strings_in_union(json=True) %}
if data is None:
return data
{% endif %}
{% if "Unset" in property.get_type_strings_in_union(json=True) %}
if isinstance(data, Unset):
return data
{% endif %}
{% set ns = namespace(contains_unmodified_properties = false) %}
{% for inner_property in property.inner_properties %}
{% import "property_templates/" + inner_property.template as inner_template %}
{% if not inner_template.construct %}
{% set ns.contains_unmodified_properties = true %}
{% continue %}
{% endif %}
{% if inner_template.check_type_for_construct and (not loop.last or ns.contains_unmodified_properties) %}
try:
if not {{ inner_template.check_type_for_construct(inner_property, "data") }}:
raise TypeError()
{{ inner_template.construct(inner_property, "data", initial_value="UNSET") | indent(8) }}
return {{ inner_property.python_name }}
except: # noqa: E722
pass
{% else %}{# Don't do try/except for the last one nor any properties with no type checking #}
{% if inner_template.check_type_for_construct %}
if not {{ inner_template.check_type_for_construct(inner_property, "data") }}:
raise TypeError()
{% endif %}
{{ inner_template.construct(inner_property, "data", initial_value="UNSET") | indent(4) }}
return {{ inner_property.python_name }}
{% endif %}
{% endfor %}
{% if ns.contains_unmodified_properties %}
return cast({{ property.get_type_string() }}, data)
{% endif %}
{{ property.python_name }} = _parse_{{ property.python_name }}({{ source }})
{% 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 %}
{% if not property.required %}
if isinstance({{ source }}, Unset):
{{ destination }} = UNSET
{% endif %}
{% endif %}
{% if property.nullable %}
{% if property.required %}
if {{ source }} is None:
{% else %}{# There's an if UNSET statement before this #}
elif {{ source }} is None:
{% 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 %}
{% set ns.contains_properties_without_transform = true %}
{% continue %}
{% 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 isinstance({{ source }}, {{ inner_property.get_instance_type_string() }}):
{% elif not loop.last or ns.contains_properties_without_transform %}
elif isinstance({{ source }}, {{ inner_property.get_instance_type_string() }}):
{% else %}
else:
{% endif %}
{{ inner_template.transform(inner_property, source, destination, declare_type=False, multipart=multipart) | indent(4) }}
{% endfor %}
{% if ns.contains_properties_without_transform and ns.contains_modified_properties %}
else:
{{ destination }} = {{ source }}
{% elif ns.contains_properties_without_transform %}
{{ destination }} = {{ source }}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,41 @@
{% if use_poetry %}
[tool.poetry]
name = "{{ project_name }}"
version = "{{ package_version }}"
description = "{{ package_description }}"
authors = []
readme = "README.md"
packages = [
{include = "{{ package_name }}"},
]
include = ["CHANGELOG.md", "{{ package_name }}/py.typed"]
[tool.poetry.dependencies]
python = "^3.7"
httpx = ">=0.15.4,<0.23.0"
attrs = ">=21.3.0"
python-dateutil = "^2.8.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
{% endif %}
[tool.black]
line-length = 120
target_version = ['py37', 'py38', 'py39']
exclude = '''
(
/(
| \.git
| \.venv
| \.mypy_cache
)/
)
'''
[tool.isort]
line_length = 120
profile = "black"

View file

@ -0,0 +1,16 @@
[tool.black]
line-length = 120
target_version = ['py36', 'py37', 'py38']
exclude = '''
(
/(
| \.git
| \.venv
| \.mypy_cache
)/
)
'''
[tool.isort]
line_length = 120
profile = "black"

View file

@ -0,0 +1,18 @@
import pathlib
from setuptools import find_packages, setup
here = pathlib.Path(__file__).parent.resolve()
long_description = (here / "README.md").read_text(encoding="utf-8")
setup(
name="{{ project_name }}",
version="{{ package_version }}",
description="{{ package_description }}",
long_description=long_description,
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"],
package_data={"{{ package_name }}": ["py.typed"]},
)

View file

@ -0,0 +1,9 @@
from enum import Enum
class {{ enum.class_info.name }}(str, Enum):
{% for key, value in enum.values.items() %}
{{ key }} = "{{ value }}"
{% endfor %}
def __str__(self) -> str:
return str(self.value)

View file

@ -0,0 +1,44 @@
""" Contains some shared types for properties """
from typing import Any, BinaryIO, Generic, MutableMapping, Optional, Tuple, TypeVar
import attr
class Unset:
def __bool__(self) -> bool:
return False
UNSET: Unset = Unset()
{# Used as `FileProperty._json_type_string` #}
FileJsonType = Tuple[Optional[str], BinaryIO, Optional[str]]
@attr.s(auto_attribs=True)
class File:
""" Contains information for file uploads """
payload: BinaryIO
file_name: Optional[str] = None
mime_type: Optional[str] = None
def to_tuple(self) -> FileJsonType:
""" Return a tuple representation that httpx will accept for multipart/form-data """
return self.file_name, self.payload, self.mime_type
T = TypeVar("T")
@attr.s(auto_attribs=True)
class Response(Generic[T]):
""" A response from an endpoint """
status_code: int
content: bytes
headers: MutableMapping[str, str]
parsed: Optional[T]
__all__ = ["File", "Response", "FileJsonType"]

2960
specs/hydra.yaml Normal file

File diff suppressed because it is too large Load diff