@@ -274,299 +274,8 @@ 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 ())
277+ client .base_url = updated_url .geturl ()
278+ return client
570279
571280
572281def v3_stable_client (client : Client ) -> Client :
0 commit comments