Skip to content

Commit

Permalink
Release v0.2.17
Browse files Browse the repository at this point in the history
  • Loading branch information
fern-api[bot] committed Mar 7, 2024
1 parent 869b3b4 commit c8d58b0
Show file tree
Hide file tree
Showing 62 changed files with 3,219 additions and 800 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v4
with:
python-version: 3.7
python-version: 3.8
- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
Expand All @@ -26,7 +26,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v4
with:
python-version: 3.7
python-version: 3.8
- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
Expand All @@ -45,7 +45,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v4
with:
python-version: 3.7
python-version: 3.8
- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
Expand Down
426 changes: 156 additions & 270 deletions poetry.lock

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "superagent-py"
version = "v0.2.16"
version = "v0.2.17"
description = ""
readme = "README.md"
authors = []
Expand All @@ -9,12 +9,13 @@ packages = [
]

[tool.poetry.dependencies]
python = "^3.7"
python = "^3.8"
httpx = ">=0.21.2"
pydantic = ">= 1.9.2, < 2.5.0"
pydantic = ">= 1.9.2"
typing_extensions = ">= 4.0.0"

[tool.poetry.dev-dependencies]
mypy = "0.971"
mypy = "^1.8.0"
pytest = "^7.4.0"

[build-system]
Expand Down
46 changes: 46 additions & 0 deletions src/superagent/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@


class Superagent:
"""
Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propogate to these functions.
Parameters:
- base_url: typing.Optional[str]. The base url to use for requests from the client.
- environment: SuperagentEnvironment. The environment to use for requests from the client. from .environment import SuperagentEnvironment
Defaults to SuperagentEnvironment.DEFAULT
- token: typing.Optional[typing.Union[str, typing.Callable[[], str]]].
- timeout: typing.Optional[float]. The timeout to be used, in seconds, for requests by default the timeout is 60 seconds.
- httpx_client: typing.Optional[httpx.Client]. The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.
---
from superagent.client import Superagent
client = Superagent(
token="YOUR_TOKEN",
)
"""

def __init__(
self,
*,
Expand All @@ -44,6 +67,29 @@ def __init__(


class AsyncSuperagent:
"""
Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propogate to these functions.
Parameters:
- base_url: typing.Optional[str]. The base url to use for requests from the client.
- environment: SuperagentEnvironment. The environment to use for requests from the client. from .environment import SuperagentEnvironment
Defaults to SuperagentEnvironment.DEFAULT
- token: typing.Optional[typing.Union[str, typing.Callable[[], str]]].
- timeout: typing.Optional[float]. The timeout to be used, in seconds, for requests by default the timeout is 60 seconds.
- httpx_client: typing.Optional[httpx.AsyncClient]. The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.
---
from superagent.client import AsyncSuperagent
client = AsyncSuperagent(
token="YOUR_TOKEN",
)
"""

def __init__(
self,
*,
Expand Down
8 changes: 8 additions & 0 deletions src/superagent/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
from .api_error import ApiError
from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper
from .datetime_utils import serialize_datetime
from .file import File, convert_file_dict_to_httpx_tuples
from .http_client import AsyncHttpClient, HttpClient
from .jsonable_encoder import jsonable_encoder
from .remove_none_from_dict import remove_none_from_dict
from .request_options import RequestOptions

__all__ = [
"ApiError",
"AsyncClientWrapper",
"AsyncHttpClient",
"BaseClientWrapper",
"File",
"HttpClient",
"RequestOptions",
"SyncClientWrapper",
"convert_file_dict_to_httpx_tuples",
"jsonable_encoder",
"remove_none_from_dict",
"serialize_datetime",
Expand Down
8 changes: 5 additions & 3 deletions src/superagent/core/client_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import httpx

from .http_client import AsyncHttpClient, HttpClient


class BaseClientWrapper:
def __init__(self, *, token: typing.Optional[typing.Union[str, typing.Callable[[], str]]] = None, base_url: str):
Expand All @@ -14,7 +16,7 @@ def get_headers(self) -> typing.Dict[str, str]:
headers: typing.Dict[str, str] = {
"X-Fern-Language": "Python",
"X-Fern-SDK-Name": "superagent-py",
"X-Fern-SDK-Version": "v0.2.16",
"X-Fern-SDK-Version": "v0.2.17",
}
token = self._get_token()
if token is not None:
Expand All @@ -40,7 +42,7 @@ def __init__(
httpx_client: httpx.Client,
):
super().__init__(token=token, base_url=base_url)
self.httpx_client = httpx_client
self.httpx_client = HttpClient(httpx_client=httpx_client)


class AsyncClientWrapper(BaseClientWrapper):
Expand All @@ -52,4 +54,4 @@ def __init__(
httpx_client: httpx.AsyncClient,
):
super().__init__(token=token, base_url=base_url)
self.httpx_client = httpx_client
self.httpx_client = AsyncHttpClient(httpx_client=httpx_client)
38 changes: 38 additions & 0 deletions src/superagent/core/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This file was auto-generated by Fern from our API Definition.

import typing

# File typing inspired by the flexibility of types within the httpx library
# https://github.com/encode/httpx/blob/master/httpx/_types.py
FileContent = typing.Union[typing.IO[bytes], bytes, str]
File = typing.Union[
# file (or bytes)
FileContent,
# (filename, file (or bytes))
typing.Tuple[typing.Optional[str], FileContent],
# (filename, file (or bytes), content_type)
typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]],
# (filename, file (or bytes), content_type, headers)
typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str], typing.Mapping[str, str]],
]


def convert_file_dict_to_httpx_tuples(
d: typing.Dict[str, typing.Union[File, typing.List[File]]]
) -> typing.List[typing.Tuple[str, File]]:
"""
The format we use is a list of tuples, where the first element is the
name of the file and the second is the file object. Typically HTTPX wants
a dict, but to be able to send lists of files, you have to use the list
approach (which also works for non-lists)
https://github.com/encode/httpx/pull/1032
"""

httpx_tuples = []
for key, file_like in d.items():
if isinstance(file_like, list):
for file_like_item in file_like:
httpx_tuples.append((key, file_like_item))
else:
httpx_tuples.append((key, file_like))
return httpx_tuples
125 changes: 125 additions & 0 deletions src/superagent/core/http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# This file was auto-generated by Fern from our API Definition.

import asyncio
import email.utils
import re
import time
import typing
from functools import wraps
from random import random

import httpx

INITIAL_RETRY_DELAY_SECONDS = 0.5
MAX_RETRY_DELAY_SECONDS = 10
MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30


def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]:
"""
This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait.
Inspired by the urllib3 retry implementation.
"""
retry_after_ms = response_headers.get("retry-after-ms")
if retry_after_ms is not None:
try:
return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0
except Exception:
pass

retry_after = response_headers.get("retry-after")
if retry_after is None:
return None

# Attempt to parse the header as an int.
if re.match(r"^\s*[0-9]+\s*$", retry_after):
seconds = float(retry_after)
# Fallback to parsing it as a date.
else:
retry_date_tuple = email.utils.parsedate_tz(retry_after)
if retry_date_tuple is None:
return None
if retry_date_tuple[9] is None: # Python 2
# Assume UTC if no timezone was specified
# On Python2.7, parsedate_tz returns None for a timezone offset
# instead of 0 if no timezone is given, where mktime_tz treats
# a None timezone offset as local time.
retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]

retry_date = email.utils.mktime_tz(retry_date_tuple)
seconds = retry_date - time.time()

if seconds < 0:
seconds = 0

return seconds


def _retry_timeout(response: httpx.Response, retries: int) -> float:
"""
Determine the amount of time to wait before retrying a request.
This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff
with a jitter to determine the number of seconds to wait.
"""

# If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
retry_after = _parse_retry_after(response.headers)
if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER:
return retry_after

# Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS.
retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS)

# Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries.
timeout = retry_delay * (1 - 0.25 * random())
return timeout if timeout >= 0 else 0


def _should_retry(response: httpx.Response) -> bool:
retriable_400s = [429, 408, 409]
return response.status_code >= 500 or response.status_code in retriable_400s


class HttpClient:
def __init__(self, *, httpx_client: httpx.Client):
self.httpx_client = httpx_client

# Ensure that the signature of the `request` method is the same as the `httpx.Client.request` method
@wraps(httpx.Client.request)
def request(
self, *args: typing.Any, max_retries: int = 0, retries: int = 0, **kwargs: typing.Any
) -> httpx.Response:
response = self.httpx_client.request(*args, **kwargs)
if _should_retry(response=response):
if max_retries > retries:
time.sleep(_retry_timeout(response=response, retries=retries))
return self.request(max_retries=max_retries, retries=retries + 1, *args, **kwargs)
return response

@wraps(httpx.Client.stream)
def stream(self, *args: typing.Any, max_retries: int = 0, retries: int = 0, **kwargs: typing.Any) -> typing.Any:
return self.httpx_client.stream(*args, **kwargs)


class AsyncHttpClient:
def __init__(self, *, httpx_client: httpx.AsyncClient):
self.httpx_client = httpx_client

# Ensure that the signature of the `request` method is the same as the `httpx.Client.request` method
@wraps(httpx.AsyncClient.request)
async def request(
self, *args: typing.Any, max_retries: int = 0, retries: int = 0, **kwargs: typing.Any
) -> httpx.Response:
response = await self.httpx_client.request(*args, **kwargs)
if _should_retry(response=response):
if max_retries > retries:
await asyncio.sleep(_retry_timeout(response=response, retries=retries))
return await self.request(max_retries=max_retries, retries=retries + 1, *args, **kwargs)
return response

@wraps(httpx.AsyncClient.request)
async def stream(
self, *args: typing.Any, max_retries: int = 0, retries: int = 0, **kwargs: typing.Any
) -> typing.Any:
return self.httpx_client.stream(*args, **kwargs)
32 changes: 32 additions & 0 deletions src/superagent/core/request_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This file was auto-generated by Fern from our API Definition.

import typing

try:
from typing import NotRequired # type: ignore
except ImportError:
from typing_extensions import NotRequired # type: ignore


class RequestOptions(typing.TypedDict):
"""
Additional options for request-specific configuration when calling APIs via the SDK.
This is used primarily as an optional final parameter for service functions.
Attributes:
- timeout_in_seconds: int. The number of seconds to await an API call before timing out.
- max_retries: int. The max number of retries to attempt if the API call fails.
- additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict
- additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict
- additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict
"""

timeout_in_seconds: NotRequired[int]
max_retries: NotRequired[int]
additional_headers: NotRequired[typing.Dict[str, typing.Any]]
additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]]
additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]]
Loading

0 comments on commit c8d58b0

Please sign in to comment.