Skip to content

Commit aad8b40

Browse files
committed
updated to latest generator
1 parent f888e35 commit aad8b40

File tree

95 files changed

+908
-1046
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+908
-1046
lines changed

custom_templates/client.py.jinja

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,57 @@
11
import base64
22
import ssl
33
import warnings
4-
from typing import Dict, Union
4+
from typing import Any, Dict, Optional, Union
5+
6+
import httpx
7+
from attrs import evolve
58

69

710
class Client:
8-
"""A class for keeping track of data related to the API
11+
"""A Client which has been authenticated for use on secured endpoints
12+
13+
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
14+
15+
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
16+
17+
``cookies``: A dictionary of cookies to be sent with every request
18+
19+
``headers``: A dictionary of headers to be sent with every request
20+
21+
``timeout``: The maximum amount of a time a request can take. API functions will raise
22+
httpx.TimeoutException if this is exceeded.
23+
24+
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
25+
but can be set to False for testing purposes.
26+
27+
``follow_redirects``: Whether or not to follow redirects. Default value is False.
28+
29+
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
30+
931

1032
Attributes:
11-
cookies: A dictionary of cookies to be sent with every request
12-
headers: A dictionary of headers to be sent with every request
13-
timeout: The maximum amount of a time in seconds a request can take. API functions will raise
14-
httpx.TimeoutException if this is exceeded.
15-
verify_ssl: Whether or not to verify the SSL certificate of the API server. This should be True in production,
16-
but can be set to False for testing purposes.
1733
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
18-
status code that was not documented in the source OpenAPI document.
19-
http2: Whether or not to use http2, enabled by default.
34+
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
35+
argument to the constructor.
36+
token: The token to use for authentication
37+
prefix: The prefix to use for the Authorization header
38+
auth_header_name: The name of the Authorization header
2039
"""
2140

22-
cookies = {}
41+
raise_on_unexpected_status: bool
42+
_base_url: str = "https://ftc-api.firstinspires.org"
43+
cookies: Dict[str, str] = {}
2344
headers: Dict[str, str] = {}
24-
timeout: float = 5.0
45+
timeout: Optional[httpx.Timeout] = None
2546
verify_ssl: Union[str, bool, ssl.SSLContext] = True
26-
raise_on_unexpected_status: bool = False
47+
follow_redirects: bool = False
48+
httpx_args: Dict[str, Any] = {}
49+
_client: Optional[httpx.Client] = None
50+
_async_client: Optional[httpx.AsyncClient] = None
51+
52+
token: str
2753
prefix: str = "Basic"
2854
auth_header_name: str = "Authorization"
29-
http2 = True
3055

3156
def __init__(self, token=None, username="", authorization_key=""):
3257
if token is not None:
@@ -37,19 +62,98 @@ class Client:
3762
base64_bytes = base64.b64encode(token_bytes)
3863
self.token = base64_bytes.decode("ascii")
3964

40-
def get_headers(self) -> Dict[str, str]:
41-
auth_header_value = f"{self.prefix} {self.token}" if self.prefix else self.token
42-
"""Get headers to be used in authenticated endpoints"""
43-
return {self.auth_header_name: auth_header_value, **self.headers}
65+
def with_headers(self, headers: Dict[str, str]) -> "Client":
66+
"""Get a new client matching this one with additional headers"""
67+
if self._client is not None:
68+
self._client.headers.update(headers)
69+
if self._async_client is not None:
70+
self._async_client.headers.update(headers)
71+
return evolve(self, headers={**self.headers, **headers})
4472

45-
def get_cookies(self) -> Dict[str, str]:
46-
return {**self.cookies}
73+
def with_cookies(self, cookies: Dict[str, str]) -> "Client":
74+
"""Get a new client matching this one with additional cookies"""
75+
if self._client is not None:
76+
self._client.cookies.update(cookies)
77+
if self._async_client is not None:
78+
self._async_client.cookies.update(cookies)
79+
return evolve(self, cookies={**self.cookies, **cookies})
4780

48-
def get_timeout(self) -> float:
49-
return self.timeout
81+
def with_timeout(self, timeout: httpx.Timeout) -> "Client":
82+
"""Get a new client matching this one with a new timeout (in seconds)"""
83+
if self._client is not None:
84+
self._client.timeout = timeout
85+
if self._async_client is not None:
86+
self._async_client.timeout = timeout
87+
return evolve(self, timeout=timeout)
88+
89+
def set_httpx_client(self, client: httpx.Client) -> "Client":
90+
"""Manually the underlying httpx.Client
91+
92+
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
93+
"""
94+
self._client = client
95+
return self
96+
97+
def get_httpx_client(self) -> httpx.Client:
98+
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
99+
if self._client is None:
100+
self.headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
101+
self._client = httpx.Client(
102+
base_url=self._base_url,
103+
cookies=self.cookies,
104+
headers=self.headers,
105+
timeout=self.timeout,
106+
verify=self.verify_ssl,
107+
follow_redirects=self.follow_redirects,
108+
**self.httpx_args,
109+
)
110+
return self._client
111+
112+
def __enter__(self) -> "Client":
113+
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
114+
self.get_httpx_client().__enter__()
115+
return self
116+
117+
def __exit__(self, *args: Any, **kwargs: Any) -> None:
118+
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
119+
self.get_httpx_client().__exit__(*args, **kwargs)
120+
121+
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
122+
"""Manually the underlying httpx.AsyncClient
123+
124+
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
125+
"""
126+
self._async_client = async_client
127+
return self
128+
129+
def get_async_httpx_client(self) -> httpx.AsyncClient:
130+
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
131+
if self._async_client is None:
132+
self.headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
133+
self._async_client = httpx.AsyncClient(
134+
base_url=self._base_url,
135+
cookies=self.cookies,
136+
headers=self.headers,
137+
timeout=self.timeout,
138+
verify=self.verify_ssl,
139+
follow_redirects=self.follow_redirects,
140+
**self.httpx_args,
141+
)
142+
return self._async_client
143+
144+
async def __aenter__(self) -> "Client":
145+
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
146+
await self.get_async_httpx_client().__aenter__()
147+
return self
148+
149+
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
150+
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
151+
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
50152

51153

52154
class AuthenticatedClient(Client):
53155
"""Deprecated, use Client instead, as it has equivalent functionality, will be removed v1.0.0"""
54-
warnings.warn("Will be removed v1.0.0 switch to Client because the functionality has been merged.",
55-
DeprecationWarning)
156+
157+
warnings.warn(
158+
"Will be removed v1.0.0 switch to Client because the functionality has been merged.", DeprecationWarning
159+
)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
{% from "property_templates/helpers.jinja" import guarded_statement %}
2+
{% from "helpers.jinja" import safe_docstring %}
3+
4+
{% macro header_params(endpoint) %}
5+
{% if endpoint.header_parameters %}
6+
headers = {}
7+
{% for parameter in endpoint.header_parameters.values() %}
8+
{% import "property_templates/" + parameter.template as param_template %}
9+
{% if param_template.transform_header %}
10+
{% set expression = param_template.transform_header(parameter.python_name) %}
11+
{% else %}
12+
{% set expression = parameter.python_name %}
13+
{% endif %}
14+
{% set statement = 'headers["' + parameter.name + '"]' + " = " + expression %}
15+
{{ guarded_statement(parameter, parameter.python_name, statement) }}
16+
{% endfor %}
17+
{% endif %}
18+
{% endmacro %}
19+
20+
{% macro cookie_params(endpoint) %}
21+
cookies = {}
22+
{% if endpoint.cookie_parameters %}
23+
{% for parameter in endpoint.cookie_parameters.values() %}
24+
{% if parameter.required %}
25+
cookies["{{ parameter.name}}"] = {{ parameter.python_name }}
26+
{% else %}
27+
if {{ parameter.python_name }} is not UNSET:
28+
cookies["{{ parameter.name}}"] = {{ parameter.python_name }}
29+
{% endif %}
30+
{% endfor %}
31+
{% endif %}
32+
{% endmacro %}
33+
34+
35+
{% macro query_params(endpoint) %}
36+
{% if endpoint.query_parameters %}
37+
params: Dict[str, Any] = {}
38+
{% for property in endpoint.query_parameters.values() %}
39+
{% set destination = property.python_name %}
40+
{% import "property_templates/" + property.template as prop_template %}
41+
{% if prop_template.transform %}
42+
{% set destination = "json_" + property.python_name %}
43+
{{ prop_template.transform(property, property.python_name, destination) }}
44+
{% endif %}
45+
{%- if not property.json_is_dict %}
46+
params["{{ property.name }}"] = {{ destination }}
47+
{% else %}
48+
{{ guarded_statement(property, destination, "params.update(" + destination + ")") }}
49+
{% endif %}
50+
51+
52+
{% endfor %}
53+
54+
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
55+
{% endif %}
56+
{% endmacro %}
57+
58+
{% macro json_body(endpoint) %}
59+
{% if endpoint.json_body %}
60+
{% set property = endpoint.json_body %}
61+
{% set destination = "json_" + property.python_name %}
62+
{% import "property_templates/" + property.template as prop_template %}
63+
{% if prop_template.transform %}
64+
{{ prop_template.transform(property, property.python_name, destination) }}
65+
{% else %}
66+
{{ destination }} = {{ property.python_name }}
67+
{% endif %}
68+
{% endif %}
69+
{% endmacro %}
70+
71+
{% macro multipart_body(endpoint) %}
72+
{% if endpoint.multipart_body %}
73+
{% set property = endpoint.multipart_body %}
74+
{% set destination = "multipart_" + property.python_name %}
75+
{% import "property_templates/" + property.template as prop_template %}
76+
{% if prop_template.transform_multipart %}
77+
{{ prop_template.transform_multipart(property, property.python_name, destination) }}
78+
{% endif %}
79+
{% endif %}
80+
{% endmacro %}
81+
82+
{# The all the kwargs passed into an endpoint (and variants thereof)) #}
83+
{% macro arguments(endpoint, include_client=True) %}
84+
{# path parameters #}
85+
{% for parameter in endpoint.path_parameters.values() %}
86+
{{ parameter.to_string() }},
87+
{% endfor %}
88+
{% if include_client or ((endpoint.list_all_parameters() | length) > (endpoint.path_parameters | length)) %}
89+
*,
90+
{% endif %}
91+
{# Proper client based on whether or not the endpoint requires authentication #}
92+
{% if include_client %}
93+
{% if endpoint.requires_security %}
94+
client: Union[AuthenticatedClient, Client],
95+
{% else %}
96+
client: Union[AuthenticatedClient, Client],
97+
{% endif %}
98+
{% endif %}
99+
{# Form data if any #}
100+
{% if endpoint.form_body %}
101+
form_data: {{ endpoint.form_body.get_type_string() }},
102+
{% endif %}
103+
{# Multipart data if any #}
104+
{% if endpoint.multipart_body %}
105+
multipart_data: {{ endpoint.multipart_body.get_type_string() }},
106+
{% endif %}
107+
{# JSON body if any #}
108+
{% if endpoint.json_body %}
109+
json_body: {{ endpoint.json_body.get_type_string() }},
110+
{% endif %}
111+
{# query parameters #}
112+
{% for parameter in endpoint.query_parameters.values() %}
113+
{{ parameter.to_string() }},
114+
{% endfor %}
115+
{% for parameter in endpoint.header_parameters.values() %}
116+
{{ parameter.to_string() }},
117+
{% endfor %}
118+
{# cookie parameters #}
119+
{% for parameter in endpoint.cookie_parameters.values() %}
120+
{{ parameter.to_string() }},
121+
{% endfor %}
122+
{% endmacro %}
123+
124+
{# Just lists all kwargs to endpoints as name=name for passing to other functions #}
125+
{% macro kwargs(endpoint, include_client=True) %}
126+
{% for parameter in endpoint.path_parameters.values() %}
127+
{{ parameter.python_name }}={{ parameter.python_name }},
128+
{% endfor %}
129+
{% if include_client %}
130+
client=client,
131+
{% endif %}
132+
{% if endpoint.form_body %}
133+
form_data=form_data,
134+
{% endif %}
135+
{% if endpoint.multipart_body %}
136+
multipart_data=multipart_data,
137+
{% endif %}
138+
{% if endpoint.json_body %}
139+
json_body=json_body,
140+
{% endif %}
141+
{% for parameter in endpoint.query_parameters.values() %}
142+
{{ parameter.python_name }}={{ parameter.python_name }},
143+
{% endfor %}
144+
{% for parameter in endpoint.header_parameters.values() %}
145+
{{ parameter.python_name }}={{ parameter.python_name }},
146+
{% endfor %}
147+
{% for parameter in endpoint.cookie_parameters.values() %}
148+
{{ parameter.python_name }}={{ parameter.python_name }},
149+
{% endfor %}
150+
{% endmacro %}
151+
152+
{% macro docstring_content(endpoint, return_string, is_detailed) %}
153+
{% if endpoint.summary %}{{ endpoint.summary | wordwrap(100)}}
154+
155+
{% endif -%}
156+
{%- if endpoint.description %} {{ endpoint.description | wordwrap(100) }}
157+
158+
{% endif %}
159+
{% if not endpoint.summary and not endpoint.description %}
160+
{# Leave extra space so that Args or Returns isn't at the top #}
161+
162+
{% endif %}
163+
{% set all_parameters = endpoint.list_all_parameters() %}
164+
{% if all_parameters %}
165+
Args:
166+
{% for parameter in all_parameters %}
167+
{{ parameter.to_docstring() | wordwrap(90) | indent(8) }}
168+
{% endfor %}
169+
170+
{% endif %}
171+
Raises:
172+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
173+
httpx.TimeoutException: If the request takes longer than Client.timeout.
174+
175+
Returns:
176+
{% if is_detailed %}
177+
Response[{{ return_string }}]
178+
{% else %}
179+
{{ return_string }}
180+
{% endif %}
181+
{% endmacro %}
182+
183+
{% macro docstring(endpoint, return_string, is_detailed) %}
184+
{{ safe_docstring(docstring_content(endpoint, return_string, is_detailed)) }}
185+
{% endmacro %}

0 commit comments

Comments
 (0)