@@ -4,10 +4,12 @@ from typing import Any, Dict, Union, Optional
4
4
import httpx
5
5
6
6
{% if config .use_dataclasses %}
7
- import copy
7
+ from copy import copy
8
8
from dataclasses import dataclass, field, replace
9
+ {% set class_header = "" %}
9
10
{% else %}
10
11
from attrs import define, field, evolve
12
+ {% set class_header = "@define" %}
11
13
{% endif %}
12
14
13
15
{% if config .use_dataclasses %}
@@ -19,27 +21,13 @@ class _HttpxConfig:
19
21
verify_ssl: bool = True
20
22
follow_redirects: bool = False
21
23
httpx_args: Dict[str, str] = field(default_factory=dict)
22
-
23
- {% macro init_args () %}
24
- self,
25
- base_url: str,
26
- cookies: Dict[str, str] = {},
27
- headers: Dict[str, str] = {},
28
- timeout: Optional[httpx.Timeout] = None,
29
- verify_ssl: bool = True,
30
- follow_redirects: bool = False,
31
- httpx_args: Dict[str, Any] = {},
32
- raise_on_unexpected_status: bool = False,
33
- {% endmacro %}
34
24
{% endif %}
35
25
36
26
{% macro httpx_client_arg (name ) -%}
37
27
{% if config .use_dataclasses %} self._httpx_config.{{ name }}{% else %} self._{{ name }}{% endif %}
38
28
{% - endmacro %}
39
29
40
- {% if not config .use_dataclasses %}
41
- @define
42
- {% endif %}
30
+ {{ class_header }}
43
31
class Client:
44
32
"""A class for keeping track of data related to the API
45
33
@@ -72,11 +60,12 @@ class Client:
72
60
{% macro attributes (extra_attrs = []) %}
73
61
{% if config .use_dataclasses %}
74
62
_base_url: str
75
- raise_on_unexpected_status: bool
76
63
{% for extra in extra_attrs %}
77
64
{{ extra }}
78
65
{% endfor %}
79
66
_httpx_config: _HttpxConfig
67
+ _client: Optional[httpx.Client]
68
+ _async_client: Optional[httpx.AsyncClient]
80
69
{% else %}
81
70
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
82
71
_base_url: str = field(alias="base_url")
@@ -95,69 +84,92 @@ class Client:
95
84
{% endfor %}
96
85
{% endif %}
97
86
{% endif %}
98
- {% endmacro %} {{ attributes() }}
87
+ {% endmacro %}
99
88
100
- {% if config .use_dataclasses %}
89
+ {{ attributes() }}
90
+
91
+ {% macro explicit_initializer (extra_attrs = []) %}
101
92
def __init__(
102
- {{ init_args() }}
93
+ self,
94
+ base_url: str,
95
+ {% for attr in extra_attrs %}
96
+ {{ attr }},
97
+ {% endfor %}
98
+ cookies: Dict[str, str] = {},
99
+ headers: Dict[str, str] = {},
100
+ timeout: Optional[httpx.Timeout] = None,
101
+ verify_ssl: bool = True,
102
+ follow_redirects: bool = False,
103
+ httpx_args: Dict[str, Any] = {},
104
+ raise_on_unexpected_status: bool = False,
103
105
) -> None:
104
106
self.raise_on_unexpected_status = raise_on_unexpected_status
105
107
self._base_url = base_url
106
108
self._httpx_config = _HttpxConfig(
107
109
cookies, headers, timeout, verify_ssl, follow_redirects, httpx_args
108
110
)
111
+ self._client = None
112
+ self._async_client = None
113
+ {% endmacro %}
114
+
115
+ {% if config .use_dataclasses %}
116
+ {{ explicit_initializer() }}
109
117
{% endif %}
110
118
111
119
{% macro builders (self ) %}
112
120
{% if config .use_dataclasses %}
113
121
def _with_httpx_config(self, httpx_config: _HttpxConfig) -> "{{ self }}":
114
- ret = copy.copy (self)
122
+ ret = copy(self)
115
123
ret._httpx_config = httpx_config
124
+ ret._client = None
125
+ ret._async_client = None
116
126
return ret
127
+ {% endif %}
117
128
118
- def with_headers(self, headers: Dict[str, str]) -> "{{ self }}":
119
- """Get a new client matching this one with additional headers"""
120
- return self._with_httpx_config(
121
- replace(self._httpx_config, headers={**self._httpx_config.headers, **headers}),
122
- )
123
-
124
- def with_cookies(self, cookies: Dict[str, str]) -> "{{ self }}":
125
- """Get a new client matching this one with additional cookies"""
126
- return self._with_httpx_config(
127
- replace(self._httpx_config, cookies={**self._httpx_config.cookies, **cookies}),
128
- )
129
-
130
- def with_timeout(self, timeout: httpx.Timeout) -> "{{ self }}":
131
- """Get a new client matching this one with a new timeout (in seconds)"""
132
- return self._with_httpx_config(replace(self._httpx_config, timeout=timeout))
133
- {% else %}
134
129
def with_headers(self, headers: Dict[str, str]) -> "{{ self }}":
135
130
"""Get a new client matching this one with additional headers"""
136
131
if self._client is not None:
137
132
self._client.headers.update(headers)
138
133
if self._async_client is not None:
139
134
self._async_client.headers.update(headers)
135
+ {% if config .use_dataclasses %}
136
+ return self._with_httpx_config(
137
+ replace(self._httpx_config, headers={**self._httpx_config.headers, **headers}),
138
+ )
139
+ {% else %}
140
140
return evolve(self, headers={**self._headers, **headers})
141
+ {% endif %}
141
142
142
143
def with_cookies(self, cookies: Dict[str, str]) -> "{{ self }}":
143
144
"""Get a new client matching this one with additional cookies"""
144
145
if self._client is not None:
145
146
self._client.cookies.update(cookies)
146
147
if self._async_client is not None:
147
148
self._async_client.cookies.update(cookies)
149
+ {% if config .use_dataclasses %}
150
+ return self._with_httpx_config(
151
+ replace(self._httpx_config, cookies={**self._httpx_config.cookies, **cookies}),
152
+ )
153
+ {% else %}
148
154
return evolve(self, cookies={**self._cookies, **cookies})
155
+ {% endif %}
149
156
150
157
def with_timeout(self, timeout: httpx.Timeout) -> "{{ self }}":
151
158
"""Get a new client matching this one with a new timeout (in seconds)"""
152
159
if self._client is not None:
153
160
self._client.timeout = timeout
154
161
if self._async_client is not None:
155
162
self._async_client.timeout = timeout
163
+ {% if config .use_dataclasses %}
164
+ return self._with_httpx_config(replace(self._httpx_config, timeout=timeout))
165
+ {% else %}
156
166
return evolve(self, timeout=timeout)
157
- {% endif %}
158
- {% endmacro %} {{ builders("Client") }}
167
+ {% endif %}
168
+ {% endmacro %}
169
+
170
+ {{ builders("Client") }}
159
171
160
- {% macro httpx_stuff (name , custom_constructor =None ) %}
172
+ {% macro httpx_stuff (name , headers_expr , update_headers =None ) %}
161
173
def set_httpx_client(self, client: httpx.Client) -> "{{ name }}":
162
174
"""Manually set the underlying httpx.Client
163
175
@@ -169,13 +181,13 @@ class Client:
169
181
def get_httpx_client(self) -> httpx.Client:
170
182
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
171
183
if self._client is None:
172
- {% if custom_constructor %}
173
- {{ custom_constructor | indent(12) }}
184
+ {% if update_headers %}
185
+ {{ update_headers | indent(12) }}
174
186
{% endif %}
175
187
self._client = httpx.Client(
176
188
base_url=self._base_url,
177
189
cookies={{ httpx_client_arg("cookies") }},
178
- headers={{ httpx_client_arg("headers") }},
190
+ headers={{ headers_expr }},
179
191
timeout={{ httpx_client_arg("timeout") }},
180
192
verify={{ httpx_client_arg("verify_ssl") }},
181
193
follow_redirects={{ httpx_client_arg("follow_redirects") }},
@@ -203,13 +215,13 @@ class Client:
203
215
def get_async_httpx_client(self) -> httpx.AsyncClient:
204
216
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
205
217
if self._async_client is None:
206
- {% if custom_constructor %}
207
- {{ custom_constructor | indent(12) }}
218
+ {% if update_headers %}
219
+ {{ update_headers | indent(12) }}
208
220
{% endif %}
209
221
self._async_client = httpx.AsyncClient(
210
222
base_url={{ httpx_client_arg("base_url") }},
211
223
cookies={{ httpx_client_arg("cookies") }},
212
- headers={{ httpx_client_arg("headers") }},
224
+ headers={{ headers_expr or httpx_client_arg("headers") }},
213
225
timeout={{ httpx_client_arg("timeout") }},
214
226
verify={{ httpx_client_arg("verify_ssl") }},
215
227
follow_redirects={{ httpx_client_arg("follow_redirects") }},
@@ -225,11 +237,15 @@ class Client:
225
237
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
226
238
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
227
239
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
228
- {% endmacro %} {{ httpx_stuff("Client") }}
240
+ {% endmacro %}
229
241
230
- {% if not config .use_dataclasses %}
231
- @define
242
+ {% if config .use_dataclasses %}
243
+ {{ httpx_stuff("Client", headers_expr="self._httpx_config.headers") }}
244
+ {% else %}
245
+ {{ httpx_stuff("Client", headers_expr="self._headers") }}
232
246
{% endif %}
247
+
248
+ {{ class_header }}
233
249
class AuthenticatedClient:
234
250
"""A Client which has been authenticated for use on secured endpoints
235
251
@@ -244,7 +260,29 @@ class AuthenticatedClient:
244
260
auth_header_name: The name of the Authorization header
245
261
"""
246
262
247
- {{ attributes(["token: str", 'prefix: str = "Bearer"', 'auth_header_name: str = "Authorization"'] ) }}
263
+ {% set extra_auth_client_args = ["token: str" , 'prefix: str = "Bearer"' , 'auth_header_name: str = "Authorization"' ] %}
264
+ {{ attributes(extra_auth_client_args) }}
265
+
266
+ {% if config .use_dataclasses %}
267
+ {{ explicit_initializer(extra_auth_client_args) }}
268
+ self.token = token
269
+ self.prefix = prefix
270
+ self.auth_header_name = auth_header_name
271
+ {% endif %}
248
272
249
273
{{ builders("AuthenticatedClient") }}
250
- {{ httpx_stuff("AuthenticatedClient", "self._headers[self.auth_header_name] = f\"{self.prefix} {self.token}\" if self.prefix else self.token") }}
274
+ {% set token_string_expr %} f"{self.prefix} {self.token}" if self.prefix else self.token {% endset %}
275
+ {% if config .use_dataclasses %}
276
+ {% set headers_expr %} { self.auth_header_name: ({{ token_string_expr }}), **self._httpx_config.headers } {% endset %}
277
+ {{ httpx_stuff(
278
+ "AuthenticatedClient",
279
+ headers_expr=headers_expr,
280
+ ) }}
281
+ {% else %}
282
+ {% set update_headers %} self._headers[self.auth_header_name] = {{ token_string_expr }}{% endset %}
283
+ {{ httpx_stuff(
284
+ "AuthenticatedClient",
285
+ headers_expr="self._headers",
286
+ update_headers=update_headers,
287
+ ) }}
288
+ {% endif %}
0 commit comments