Skip to content

Commit

Permalink
feat(api): codegen changes
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-app[bot] authored and Stainless Bot committed Jul 12, 2024
1 parent 2f3b3f1 commit 08baa7a
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}

USER vscode

RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.24.0" RYE_INSTALL_OPTION="--yes" bash
RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
ENV PATH=/home/vscode/.rye/shims:$PATH

RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: 0.24.0
RYE_VERSION: '0.35.0'
RYE_INSTALL_OPTION: '--yes'

- name: Install dependencies
Expand All @@ -42,7 +42,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: 0.24.0
RYE_VERSION: '0.35.0'
RYE_INSTALL_OPTION: '--yes'

- name: Bootstrap
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: 0.24.0
RYE_INSTALL_OPTION: "--yes"
RYE_VERSION: '0.35.0'
RYE_INSTALL_OPTION: '--yes'

- name: Publish to PyPI
run: |
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ dev-dependencies = [
"nox",
"dirty-equals>=0.6.0",
"importlib-metadata>=6.7.0",
"rich>=13.7.1",

]

Expand Down
4 changes: 4 additions & 0 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# features: []
# all-features: true
# with-sources: false
# generate-hashes: false

-e file:.
annotated-types==0.6.0
Expand Down Expand Up @@ -67,6 +68,8 @@ pydantic==2.7.1
# via writer-sdk
pydantic-core==2.18.2
# via pydantic
pygments==2.18.0
# via rich
pyright==1.1.364
pytest==7.1.1
# via pytest-asyncio
Expand All @@ -76,6 +79,7 @@ python-dateutil==2.8.2
pytz==2023.3.post1
# via dirty-equals
respx==0.20.2
rich==13.7.1
ruff==0.1.9
setuptools==68.2.2
# via nodeenv
Expand Down
1 change: 1 addition & 0 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# features: []
# all-features: true
# with-sources: false
# generate-hashes: false

-e file:.
annotated-types==0.6.0
Expand Down
42 changes: 34 additions & 8 deletions src/writerai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
HttpxSendArgs,
AsyncTransport,
RequestOptions,
HttpxRequestFiles,
ModelBuilderProtocol,
)
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
Expand Down Expand Up @@ -459,6 +460,7 @@ def _build_request(
headers = self._build_headers(options)
params = _merge_mappings(self.default_query, options.params)
content_type = headers.get("Content-Type")
files = options.files

# If the given Content-Type header is multipart/form-data then it
# has to be removed so that httpx can generate the header with
Expand All @@ -472,14 +474,23 @@ def _build_request(
headers.pop("Content-Type")

# As we are now sending multipart/form-data instead of application/json
# we need to tell httpx to use it, https://www.python-httpx.org/advanced/#multipart-file-encoding
# we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
if json_data:
if not is_dict(json_data):
raise TypeError(
f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
)
kwargs["data"] = self._serialize_multipartform(json_data)

# httpx determines whether or not to send a "multipart/form-data"
# request based on the truthiness of the "files" argument.
# This gets around that issue by generating a dict value that
# evaluates to true.
#
# https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
if not files:
files = cast(HttpxRequestFiles, ForceMultipartDict())

# TODO: report this error to httpx
return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
headers=headers,
Expand All @@ -492,7 +503,7 @@ def _build_request(
# https://github.com/microsoft/pyright/issues/3526#event-6715453066
params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
json=json_data,
files=options.files,
files=files,
**kwargs,
)

Expand Down Expand Up @@ -944,6 +955,11 @@ def _request(
stream: bool,
stream_cls: type[_StreamT] | None,
) -> ResponseT | _StreamT:
# create a copy of the options we were given so that if the
# options are mutated later & we then retry, the retries are
# given the original options
input_options = model_copy(options)

cast_to = self._maybe_override_cast_to(cast_to, options)
self._prepare_options(options)

Expand All @@ -968,7 +984,7 @@ def _request(

if retries > 0:
return self._retry_request(
options,
input_options,
cast_to,
retries,
stream=stream,
Expand All @@ -983,7 +999,7 @@ def _request(

if retries > 0:
return self._retry_request(
options,
input_options,
cast_to,
retries,
stream=stream,
Expand Down Expand Up @@ -1011,7 +1027,7 @@ def _request(
if retries > 0 and self._should_retry(err.response):
err.response.close()
return self._retry_request(
options,
input_options,
cast_to,
retries,
err.response.headers,
Expand Down Expand Up @@ -1507,6 +1523,11 @@ async def _request(
# execute it earlier while we are in an async context
self._platform = await asyncify(get_platform)()

# create a copy of the options we were given so that if the
# options are mutated later & we then retry, the retries are
# given the original options
input_options = model_copy(options)

cast_to = self._maybe_override_cast_to(cast_to, options)
await self._prepare_options(options)

Expand All @@ -1529,7 +1550,7 @@ async def _request(

if retries > 0:
return await self._retry_request(
options,
input_options,
cast_to,
retries,
stream=stream,
Expand All @@ -1544,7 +1565,7 @@ async def _request(

if retries > 0:
return await self._retry_request(
options,
input_options,
cast_to,
retries,
stream=stream,
Expand All @@ -1567,7 +1588,7 @@ async def _request(
if retries > 0 and self._should_retry(err.response):
await err.response.aclose()
return await self._retry_request(
options,
input_options,
cast_to,
retries,
err.response.headers,
Expand Down Expand Up @@ -1863,6 +1884,11 @@ def make_request_options(
return options


class ForceMultipartDict(Dict[str, None]):
def __bool__(self) -> bool:
return True


class OtherPlatform:
def __init__(self, name: str) -> None:
self.name = name
Expand Down
27 changes: 27 additions & 0 deletions src/writerai/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ClassVar,
Protocol,
Required,
ParamSpec,
TypedDict,
TypeGuard,
final,
Expand Down Expand Up @@ -67,6 +68,9 @@
__all__ = ["BaseModel", "GenericModel"]

_T = TypeVar("_T")
_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")

P = ParamSpec("P")


@runtime_checkable
Expand Down Expand Up @@ -379,6 +383,29 @@ def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericMo
return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)


def build(
base_model_cls: Callable[P, _BaseModelT],
*args: P.args,
**kwargs: P.kwargs,
) -> _BaseModelT:
"""Construct a BaseModel class without validation.
This is useful for cases where you need to instantiate a `BaseModel`
from an API response as this provides type-safe params which isn't supported
by helpers like `construct_type()`.
```py
build(MyModel, my_field_a="foo", my_field_b=123)
```
"""
if args:
raise TypeError(
"Received positional arguments which are not supported; Keyword arguments must be used instead",
)

return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))


def construct_type(*, value: object, type_: object) -> object:
"""Loose coercion to the expected type with construction of nested values.
Expand Down

0 comments on commit 08baa7a

Please sign in to comment.