Skip to content

Commit d81bfa8

Browse files
Update tests again for spaces
1 parent b2ab763 commit d81bfa8

File tree

3 files changed

+311
-0
lines changed
  • end_to_end_tests
    • golden-record/my_test_api_client
    • literal-enums-golden-record/my_enum_api_client
    • test-3-1-golden-record/test_3_1_features_client

3 files changed

+311
-0
lines changed

end_to_end_tests/golden-record/my_test_api_client/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
268268
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
269269
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
270270

271+
271272
def replace_client_path(client: Client, base_path: str) -> Client:
272273
"""Override a client's base URL with a new path. Does not update scheme, host, or other URL parts."""
273274
parsed = urllib.parse.urlparse(client.base_url)

end_to_end_tests/literal-enums-golden-record/my_enum_api_client/client.py

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,311 @@ def replace_client_path(client: Client, base_path: str) -> Client:
274274
parsed = urllib.parse.urlparse(client.base_url)
275275
# _replace is not private, it's part of the NamedTuple API but prefixed _ to avoid conflicts
276276
updated_url = parsed._replace(path=base_path)
277+
return client.with_base_url(updated_url.geturl())
278+
279+
280+
def v3_stable_client(client: Client) -> Client:
281+
"""Override a client's base URL with a v2 stable path."""
282+
return replace_client_path(client, "api/v3-draft")
283+
284+
285+
def v3_alpha_client(client: Client) -> Client:
286+
"""Override a client's base URL with a v2-alpha path."""
287+
return replace_client_path(client, "api/v3-alpha")
288+
289+
290+
def v3_beta_client(client: Client) -> Client:
291+
"""Override a client's base URL with a v2-beta path."""
292+
return replace_client_path(client, "api/v3-beta")
293+
import ssl
294+
from typing import Any, Optional, Union
295+
296+
import httpx
297+
from attrs import define, evolve, field
298+
import urllib.parse
299+
300+
301+
@define
302+
class Client:
303+
"""A class for keeping track of data related to the API
304+
305+
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
306+
307+
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
308+
309+
``cookies``: A dictionary of cookies to be sent with every request
310+
311+
``headers``: A dictionary of headers to be sent with every request
312+
313+
``timeout``: The maximum amount of a time a request can take. API functions will raise
314+
httpx.TimeoutException if this is exceeded.
315+
316+
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
317+
but can be set to False for testing purposes.
318+
319+
``follow_redirects``: Whether or not to follow redirects. Default value is False.
320+
321+
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
322+
323+
324+
Attributes:
325+
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
326+
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
327+
argument to the constructor.
328+
"""
329+
330+
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
331+
_base_url: str = field(alias="base_url")
332+
_cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
333+
_headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
334+
_timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout")
335+
_verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl")
336+
_follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
337+
_httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
338+
_client: Optional[httpx.Client] = field(default=None, init=False)
339+
_async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
340+
341+
def with_headers(self, headers: dict[str, str]) -> "Client":
342+
"""Get a new client matching this one with additional headers"""
343+
if self._client is not None:
344+
self._client.headers.update(headers)
345+
if self._async_client is not None:
346+
self._async_client.headers.update(headers)
347+
return evolve(self, headers={**self._headers, **headers})
348+
349+
def with_cookies(self, cookies: dict[str, str]) -> "Client":
350+
"""Get a new client matching this one with additional cookies"""
351+
if self._client is not None:
352+
self._client.cookies.update(cookies)
353+
if self._async_client is not None:
354+
self._async_client.cookies.update(cookies)
355+
return evolve(self, cookies={**self._cookies, **cookies})
356+
357+
def with_timeout(self, timeout: httpx.Timeout) -> "Client":
358+
"""Get a new client matching this one with a new timeout (in seconds)"""
359+
if self._client is not None:
360+
self._client.timeout = timeout
361+
if self._async_client is not None:
362+
self._async_client.timeout = timeout
363+
return evolve(self, timeout=timeout)
364+
365+
def set_httpx_client(self, client: httpx.Client) -> "Client":
366+
"""Manually set the underlying httpx.Client
367+
368+
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
369+
"""
370+
self._client = client
371+
return self
372+
373+
def get_httpx_client(self) -> httpx.Client:
374+
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
375+
if self._client is None:
376+
self._client = httpx.Client(
377+
base_url=self._base_url,
378+
cookies=self._cookies,
379+
headers=self._headers,
380+
timeout=self._timeout,
381+
verify=self._verify_ssl,
382+
follow_redirects=self._follow_redirects,
383+
**self._httpx_args,
384+
)
385+
return self._client
386+
387+
def __enter__(self) -> "Client":
388+
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
389+
self.get_httpx_client().__enter__()
390+
return self
391+
392+
def __exit__(self, *args: Any, **kwargs: Any) -> None:
393+
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
394+
self.get_httpx_client().__exit__(*args, **kwargs)
395+
396+
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
397+
"""Manually the underlying httpx.AsyncClient
398+
399+
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
400+
"""
401+
self._async_client = async_client
402+
return self
403+
404+
def get_async_httpx_client(self) -> httpx.AsyncClient:
405+
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
406+
if self._async_client is None:
407+
self._async_client = httpx.AsyncClient(
408+
base_url=self._base_url,
409+
cookies=self._cookies,
410+
headers=self._headers,
411+
timeout=self._timeout,
412+
verify=self._verify_ssl,
413+
follow_redirects=self._follow_redirects,
414+
**self._httpx_args,
415+
)
416+
return self._async_client
417+
418+
async def __aenter__(self) -> "Client":
419+
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
420+
await self.get_async_httpx_client().__aenter__()
421+
return self
422+
423+
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
424+
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
425+
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
426+
427+
428+
@define
429+
class AuthenticatedClient:
430+
"""A Client which has been authenticated for use on secured endpoints
431+
432+
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
433+
434+
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
435+
436+
``cookies``: A dictionary of cookies to be sent with every request
437+
438+
``headers``: A dictionary of headers to be sent with every request
439+
440+
``timeout``: The maximum amount of a time a request can take. API functions will raise
441+
httpx.TimeoutException if this is exceeded.
442+
443+
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
444+
but can be set to False for testing purposes.
445+
446+
``follow_redirects``: Whether or not to follow redirects. Default value is False.
447+
448+
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
449+
450+
451+
Attributes:
452+
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
453+
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
454+
argument to the constructor.
455+
token: The token to use for authentication
456+
prefix: The prefix to use for the Authorization header
457+
auth_header_name: The name of the Authorization header
458+
"""
459+
460+
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
461+
_base_url: str = field(alias="base_url")
462+
_cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
463+
_headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
464+
_timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout")
465+
_verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl")
466+
_follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
467+
_httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
468+
_client: Optional[httpx.Client] = field(default=None, init=False)
469+
_async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
470+
471+
token: str
472+
prefix: str = "Bearer"
473+
auth_header_name: str = "Authorization"
474+
475+
def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient":
476+
"""Get a new client matching this one with additional headers"""
477+
if self._client is not None:
478+
self._client.headers.update(headers)
479+
if self._async_client is not None:
480+
self._async_client.headers.update(headers)
481+
return evolve(self, headers={**self._headers, **headers})
482+
483+
def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient":
484+
"""Get a new client matching this one with additional cookies"""
485+
if self._client is not None:
486+
self._client.cookies.update(cookies)
487+
if self._async_client is not None:
488+
self._async_client.cookies.update(cookies)
489+
return evolve(self, cookies={**self._cookies, **cookies})
490+
491+
def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient":
492+
"""Get a new client matching this one with a new timeout (in seconds)"""
493+
if self._client is not None:
494+
self._client.timeout = timeout
495+
if self._async_client is not None:
496+
self._async_client.timeout = timeout
497+
return evolve(self, timeout=timeout)
498+
499+
def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
500+
"""Manually set the underlying httpx.Client
501+
502+
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
503+
"""
504+
self._client = client
505+
return self
506+
507+
def get_httpx_client(self) -> httpx.Client:
508+
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
509+
if self._client is None:
510+
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
511+
self._client = httpx.Client(
512+
base_url=self._base_url,
513+
cookies=self._cookies,
514+
headers=self._headers,
515+
timeout=self._timeout,
516+
verify=self._verify_ssl,
517+
follow_redirects=self._follow_redirects,
518+
**self._httpx_args,
519+
)
520+
return self._client
521+
522+
def __enter__(self) -> "AuthenticatedClient":
523+
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
524+
self.get_httpx_client().__enter__()
525+
return self
526+
527+
def __exit__(self, *args: Any, **kwargs: Any) -> None:
528+
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
529+
self.get_httpx_client().__exit__(*args, **kwargs)
530+
531+
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient":
532+
"""Manually the underlying httpx.AsyncClient
533+
534+
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
535+
"""
536+
self._async_client = async_client
537+
return self
538+
539+
def get_async_httpx_client(self) -> httpx.AsyncClient:
540+
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
541+
if self._async_client is None:
542+
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
543+
self._async_client = httpx.AsyncClient(
544+
base_url=self._base_url,
545+
cookies=self._cookies,
546+
headers=self._headers,
547+
timeout=self._timeout,
548+
verify=self._verify_ssl,
549+
follow_redirects=self._follow_redirects,
550+
**self._httpx_args,
551+
)
552+
return self._async_client
553+
554+
async def __aenter__(self) -> "AuthenticatedClient":
555+
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
556+
await self.get_async_httpx_client().__aenter__()
557+
return self
558+
559+
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
560+
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
561+
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
562+
563+
564+
def replace_client_path(client: Client, base_path: str) -> Client:
565+
"""Override a client's base URL with a new path. Does not update scheme, host, or other URL parts."""
566+
parsed = urllib.parse.urlparse(client.base_url)
567+
# _replace is not private, it's part of the NamedTuple API but prefixed _ to avoid conflicts
568+
updated_url = parsed._replace(path=base_path)
569+
return client.with_base_url(updated_url.geturl())
570+
571+
572+
def v3_stable_client(client: Client) -> Client:
573+
"""Override a client's base URL with a v2 stable path."""
574+
return replace_client_path(client, "api/v3-draft")
575+
576+
577+
def v3_alpha_client(client: Client) -> Client:
578+
"""Override a client's base URL with a v2-alpha path."""
579+
return replace_client_path(client, "api/v3-alpha")
580+
581+
582+
def v3_beta_client(client: Client) -> Client:
583+
"""Override a client's base URL with a v2-beta path."""
584+
return replace_client_path(client, "api/v3-beta")

end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from attrs import define, evolve, field
66
import urllib.parse
77

8+
89
@define
910
class Client:
1011
"""A class for keeping track of data related to the API
@@ -267,6 +268,7 @@ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
267268
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
268269
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
269270

271+
270272
def replace_client_path(client: Client, base_path: str) -> Client:
271273
"""Override a client's base URL with a new path. Does not update scheme, host, or other URL parts."""
272274
parsed = urllib.parse.urlparse(client.base_url)

0 commit comments

Comments
 (0)