Skip to content

Commit

Permalink
updated to latest generator
Browse files Browse the repository at this point in the history
  • Loading branch information
arihant2math committed Oct 17, 2023
1 parent f888e35 commit aad8b40
Show file tree
Hide file tree
Showing 95 changed files with 908 additions and 1,046 deletions.
152 changes: 128 additions & 24 deletions custom_templates/client.py.jinja
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
import base64
import ssl
import warnings
from typing import Dict, Union
from typing import Any, Dict, Optional, Union

import httpx
from attrs import evolve


class Client:
"""A class for keeping track of data related to the API
"""A Client which has been authenticated for use on secured endpoints

The following are accepted as keyword arguments and will be used to construct httpx Clients internally:

``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 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.

``follow_redirects``: Whether or not to follow redirects. Default value is False.

``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.


Attributes:
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.
http2: Whether or not to use http2, enabled by default.
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
argument to the constructor.
token: The token to use for authentication
prefix: The prefix to use for the Authorization header
auth_header_name: The name of the Authorization header
"""

cookies = {}
raise_on_unexpected_status: bool
_base_url: str = "https://ftc-api.firstinspires.org"
cookies: Dict[str, str] = {}
headers: Dict[str, str] = {}
timeout: float = 5.0
timeout: Optional[httpx.Timeout] = None
verify_ssl: Union[str, bool, ssl.SSLContext] = True
raise_on_unexpected_status: bool = False
follow_redirects: bool = False
httpx_args: Dict[str, Any] = {}
_client: Optional[httpx.Client] = None
_async_client: Optional[httpx.AsyncClient] = None

token: str
prefix: str = "Basic"
auth_header_name: str = "Authorization"
http2 = True

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

def get_headers(self) -> Dict[str, str]:
auth_header_value = f"{self.prefix} {self.token}" if self.prefix else self.token
"""Get headers to be used in authenticated endpoints"""
return {self.auth_header_name: auth_header_value, **self.headers}
def with_headers(self, headers: Dict[str, str]) -> "Client":
"""Get a new client matching this one with additional headers"""
if self._client is not None:
self._client.headers.update(headers)
if self._async_client is not None:
self._async_client.headers.update(headers)
return 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"""
if self._client is not None:
self._client.cookies.update(cookies)
if self._async_client is not None:
self._async_client.cookies.update(cookies)
return evolve(self, cookies={**self.cookies, **cookies})

def get_timeout(self) -> float:
return self.timeout
def with_timeout(self, timeout: httpx.Timeout) -> "Client":
"""Get a new client matching this one with a new timeout (in seconds)"""
if self._client is not None:
self._client.timeout = timeout
if self._async_client is not None:
self._async_client.timeout = timeout
return evolve(self, timeout=timeout)

def set_httpx_client(self, client: httpx.Client) -> "Client":
"""Manually the underlying httpx.Client

**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
"""
self._client = client
return self

def get_httpx_client(self) -> httpx.Client:
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
if self._client is None:
self.headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
self._client = httpx.Client(
base_url=self._base_url,
cookies=self.cookies,
headers=self.headers,
timeout=self.timeout,
verify=self.verify_ssl,
follow_redirects=self.follow_redirects,
**self.httpx_args,
)
return self._client

def __enter__(self) -> "Client":
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
self.get_httpx_client().__enter__()
return self

def __exit__(self, *args: Any, **kwargs: Any) -> None:
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
self.get_httpx_client().__exit__(*args, **kwargs)

def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
"""Manually the underlying httpx.AsyncClient

**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
"""
self._async_client = async_client
return self

def get_async_httpx_client(self) -> httpx.AsyncClient:
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
if self._async_client is None:
self.headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
self._async_client = httpx.AsyncClient(
base_url=self._base_url,
cookies=self.cookies,
headers=self.headers,
timeout=self.timeout,
verify=self.verify_ssl,
follow_redirects=self.follow_redirects,
**self.httpx_args,
)
return self._async_client

async def __aenter__(self) -> "Client":
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
await self.get_async_httpx_client().__aenter__()
return self

async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
await self.get_async_httpx_client().__aexit__(*args, **kwargs)


class AuthenticatedClient(Client):
"""Deprecated, use Client instead, as it has equivalent functionality, will be removed v1.0.0"""
warnings.warn("Will be removed v1.0.0 switch to Client because the functionality has been merged.",
DeprecationWarning)

warnings.warn(
"Will be removed v1.0.0 switch to Client because the functionality has been merged.", DeprecationWarning
)
185 changes: 185 additions & 0 deletions custom_templates/endpoint_macros.py.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
{% from "property_templates/helpers.jinja" import guarded_statement %}
{% from "helpers.jinja" import safe_docstring %}

{% macro header_params(endpoint) %}
{% if endpoint.header_parameters %}
headers = {}
{% for parameter in endpoint.header_parameters.values() %}
{% import "property_templates/" + parameter.template as param_template %}
{% if param_template.transform_header %}
{% set expression = param_template.transform_header(parameter.python_name) %}
{% else %}
{% set expression = parameter.python_name %}
{% endif %}
{% set statement = 'headers["' + parameter.name + '"]' + " = " + expression %}
{{ guarded_statement(parameter, parameter.python_name, statement) }}
{% endfor %}
{% endif %}
{% endmacro %}

{% macro cookie_params(endpoint) %}
cookies = {}
{% 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, include_client=True) %}
{# path parameters #}
{% for parameter in endpoint.path_parameters.values() %}
{{ parameter.to_string() }},
{% endfor %}
{% if include_client or ((endpoint.list_all_parameters() | length) > (endpoint.path_parameters | length)) %}
*,
{% endif %}
{# Proper client based on whether or not the endpoint requires authentication #}
{% if include_client %}
{% if endpoint.requires_security %}
client: Union[AuthenticatedClient, Client],
{% else %}
client: Union[AuthenticatedClient, Client],
{% endif %}
{% endif %}
{# Form data if any #}
{% if endpoint.form_body %}
form_data: {{ endpoint.form_body.get_type_string() }},
{% 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, include_client=True) %}
{% for parameter in endpoint.path_parameters.values() %}
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
{% if include_client %}
client=client,
{% endif %}
{% if endpoint.form_body %}
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_content(endpoint, return_string, is_detailed) %}
{% 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 %}
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:
{% if is_detailed %}
Response[{{ return_string }}]
{% else %}
{{ return_string }}
{% endif %}
{% endmacro %}

{% macro docstring(endpoint, return_string, is_detailed) %}
{{ safe_docstring(docstring_content(endpoint, return_string, is_detailed)) }}
{% endmacro %}
Loading

0 comments on commit aad8b40

Please sign in to comment.