Skip to content

release: 0.6.0 #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 5 additions & 23 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
# syntax=docker/dockerfile:1
FROM debian:bookworm-slim
ARG VARIANT="3.9"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}

RUN apt-get update && apt-get install -y \
libxkbcommon0 \
ca-certificates \
make \
curl \
git \
unzip \
libc++1 \
vim \
termcap \
&& apt-get clean autoclean
USER vscode

RUN curl -sSf https://rye-up.com/get | RYE_VERSION="0.15.2" RYE_INSTALL_OPTION="--yes" bash
ENV PATH=/root/.rye/shims:$PATH
ENV PATH=/home/vscode/.rye/shims:$PATH

WORKDIR /workspace

COPY README.md .python-version pyproject.toml requirements.lock requirements-dev.lock /workspace/

RUN rye sync --all-features

COPY . /workspace

CMD ["rye", "shell"]
RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc
21 changes: 20 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,26 @@
{
"name": "Debian",
"build": {
"dockerfile": "Dockerfile"
"dockerfile": "Dockerfile",
"context": ".."
},

"postStartCommand": "rye sync --all-features",

"customizations": {
"vscode": {
"extensions": [
"ms-python.python"
],
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": ".venv/bin/python",
"python.typeChecking": "basic",
"terminal.integrated.env.linux": {
"PATH": "/home/vscode/.rye/shims:${env:PATH}"
}
}
}
}

// Features to add to the dev container. More info: https://containers.dev/features.
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.5.0"
".": "0.6.0"
}
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Changelog

## 0.6.0 (2023-11-08)

Full Changelog: [v0.5.0...v0.6.0](https://github.com/Finch-API/finch-api-python/compare/v0.5.0...v0.6.0)

### Features

* **client:** allow binary returns ([#164](https://github.com/Finch-API/finch-api-python/issues/164)) ([6322114](https://github.com/Finch-API/finch-api-python/commit/6322114c6e752686d4d3f502df94339037a44698))
* **client:** support passing BaseModels to request params at runtime ([#166](https://github.com/Finch-API/finch-api-python/issues/166)) ([f3e7e76](https://github.com/Finch-API/finch-api-python/commit/f3e7e763c9e1809b6d84e78720465a96daa11ec2))
* **client:** support passing httpx.Timeout to method timeout argument ([#171](https://github.com/Finch-API/finch-api-python/issues/171)) ([a3dcd4b](https://github.com/Finch-API/finch-api-python/commit/a3dcd4b1add4499894bfc4f63dfe3f46b9a3a2e9))


### Bug Fixes

* **binaries:** don't synchronously block in astream_to_file ([#167](https://github.com/Finch-API/finch-api-python/issues/167)) ([c607d54](https://github.com/Finch-API/finch-api-python/commit/c607d545379a03d0926aef998e246fd33009d716))
* prevent TypeError in Python 3.8 (ABC is not subscriptable) ([#170](https://github.com/Finch-API/finch-api-python/issues/170)) ([fc12d83](https://github.com/Finch-API/finch-api-python/commit/fc12d835d80d6d7f076aa81ecfb5fcc318c80261))


### Chores

* **docs:** fix github links ([#173](https://github.com/Finch-API/finch-api-python/issues/173)) ([9a8407d](https://github.com/Finch-API/finch-api-python/commit/9a8407df5c7e44c3466c2fb860fcbd0d162a9d40))
* **docs:** fix some typos ([#172](https://github.com/Finch-API/finch-api-python/issues/172)) ([19702d1](https://github.com/Finch-API/finch-api-python/commit/19702d1b40f8e3fbfdf35d1da0645a14c422cdb7))
* **internal:** improve github devcontainer setup ([#174](https://github.com/Finch-API/finch-api-python/issues/174)) ([5334b9c](https://github.com/Finch-API/finch-api-python/commit/5334b9cb09e65c07495a4b8ae3910547e0121539))
* **internal:** remove unused int/float conversion ([#168](https://github.com/Finch-API/finch-api-python/issues/168)) ([69faf73](https://github.com/Finch-API/finch-api-python/commit/69faf737601c196eb3fa53fa2575c9c3a53a0d28))


### Documentation

* **readme:** improve example snippets ([#169](https://github.com/Finch-API/finch-api-python/issues/169)) ([37fe000](https://github.com/Finch-API/finch-api-python/commit/37fe0007d4c1643a14aae4e580ebebf537963399))

## 0.5.0 (2023-10-31)

Full Changelog: [v0.4.0...v0.5.0](https://github.com/Finch-API/finch-api-python/compare/v0.4.0...v0.5.0)
Expand Down
32 changes: 12 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,26 @@ client = Finch(
access_token="My Access Token",
)

page = client.hris.directory.list(
candidate_id="<candidate id>",
)
directory = page.individuals[0]
print(directory.first_name)
page = client.hris.directory.list()
print(page.page)
```

## Async usage

Simply import `AsyncFinch` instead of `Finch` and use `await` with each API call:

```python
import asyncio
from finch import AsyncFinch

client = AsyncFinch(
access_token="My Access Token",
)


async def main():
page = await client.hris.directory.list(
candidate_id="<candidate id>",
)
print(page.individuals[0].first_name)
async def main() -> None:
page = await client.hris.directory.list()
print(page.page)


asyncio.run(main())
Expand Down Expand Up @@ -138,10 +134,8 @@ from finch import Finch

client = Finch()

client.hris.directory.list(
path_params=[],
params={},
)
page = client.hris.directory.list()
print(page.page)
```

## Webhook Verification
Expand Down Expand Up @@ -183,7 +177,7 @@ from finch import Finch
client = Finch()

try:
client.hris.directory.list()
client.hris.company.retrieve()
except finch.APIConnectionError as e:
print("The server could not be reached")
print(e.__cause__) # an underlying Exception, likely raised within httpx.
Expand Down Expand Up @@ -304,16 +298,14 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to
from finch import Finch

client = Finch()
page = client.hris.directory.with_raw_response.list()
response = page.individuals[0]

response = client.hris.directory.with_raw_response.list()
print(response.headers.get('X-My-Header'))

directory = response.parse() # get the object that `hris.directory.list()` would have returned
print(directory.first_name)
print(directory.id)
```

These methods return an [`APIResponse`](https://github.com/Finch-API/finch-api-python/src/finch/_response.py) object.
These methods return an [`APIResponse`](https://github.com/Finch-API/finch-api-python/tree/main/src/finch/_response.py) object.

### Configuring the HTTP client

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "finch-api"
version = "0.5.0"
version = "0.6.0"
description = "Client library for the Finch API"
readme = "README.md"
license = "Apache-2.0"
Expand Down
96 changes: 95 additions & 1 deletion src/finch/_base_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os
import json
import time
import uuid
Expand Down Expand Up @@ -60,6 +61,7 @@
RequestOptions,
UnknownResponse,
ModelBuilderProtocol,
BinaryResponseContent,
)
from ._utils import is_dict, is_given, is_mapping
from ._compat import model_copy, model_dump
Expand Down Expand Up @@ -1535,7 +1537,7 @@ def make_request_options(
extra_query: Query | None = None,
extra_body: Body | None = None,
idempotency_key: str | None = None,
timeout: float | None | NotGiven = NOT_GIVEN,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
post_parser: PostParser | NotGiven = NOT_GIVEN,
) -> RequestOptions:
"""Create a dict of type RequestOptions without keys of NotGiven values."""
Expand Down Expand Up @@ -1672,3 +1674,95 @@ def _merge_mappings(
"""
merged = {**obj1, **obj2}
return {key: value for key, value in merged.items() if not isinstance(value, Omit)}


class HttpxBinaryResponseContent(BinaryResponseContent):
response: httpx.Response

def __init__(self, response: httpx.Response) -> None:
self.response = response

@property
@override
def content(self) -> bytes:
return self.response.content

@property
@override
def text(self) -> str:
return self.response.text

@property
@override
def encoding(self) -> Optional[str]:
return self.response.encoding

@property
@override
def charset_encoding(self) -> Optional[str]:
return self.response.charset_encoding

@override
def json(self, **kwargs: Any) -> Any:
return self.response.json(**kwargs)

@override
def read(self) -> bytes:
return self.response.read()

@override
def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]:
return self.response.iter_bytes(chunk_size)

@override
def iter_text(self, chunk_size: Optional[int] = None) -> Iterator[str]:
return self.response.iter_text(chunk_size)

@override
def iter_lines(self) -> Iterator[str]:
return self.response.iter_lines()

@override
def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]:
return self.response.iter_raw(chunk_size)

@override
def stream_to_file(self, file: str | os.PathLike[str]) -> None:
with open(file, mode="wb") as f:
for data in self.response.iter_bytes():
f.write(data)

@override
def close(self) -> None:
return self.response.close()

@override
async def aread(self) -> bytes:
return await self.response.aread()

@override
async def aiter_bytes(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]:
return self.response.aiter_bytes(chunk_size)

@override
async def aiter_text(self, chunk_size: Optional[int] = None) -> AsyncIterator[str]:
return self.response.aiter_text(chunk_size)

@override
async def aiter_lines(self) -> AsyncIterator[str]:
return self.response.aiter_lines()

@override
async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]:
return self.response.aiter_raw(chunk_size)

@override
async def astream_to_file(self, file: str | os.PathLike[str]) -> None:
path = anyio.Path(file)
async with await path.open(mode="wb") as f:
async for data in self.response.aiter_bytes():
await f.write(data)

@override
async def aclose(self) -> None:
return await self.response.aclose()
4 changes: 2 additions & 2 deletions src/finch/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ def get_auth_url(
) -> str:
"""
Returns the authorization url which can be visited in order to obtain an
authorization code from Finch. The autorization code can then be exchanged for
authorization code from Finch. The authorization code can then be exchanged for
an access token for the Finch api by calling get_access_token().
"""
if self.client_id is None:
Expand Down Expand Up @@ -602,7 +602,7 @@ def get_auth_url(
) -> str:
"""
Returns the authorization url which can be visited in order to obtain an
authorization code from Finch. The autorization code can then be exchanged for
authorization code from Finch. The authorization code can then be exchanged for
an access token for the Finch api by calling get_access_token().
"""
if self.client_id is None:
Expand Down
15 changes: 6 additions & 9 deletions src/finch/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,16 +313,13 @@ def construct_type(*, value: object, type_: type) -> object:
return [construct_type(value=entry, type_=inner_type) for entry in value]

if origin == float:
try:
return float(cast(Any, value))
except Exception:
return value
if isinstance(value, int):
coerced = float(value)
if coerced != value:
return value
return coerced

if origin == int:
try:
return int(cast(Any, value))
except Exception:
return value
return value

if type_ == datetime:
try:
Expand Down
5 changes: 4 additions & 1 deletion src/finch/_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import httpx
import pydantic

from ._types import NoneType, UnknownResponse
from ._types import NoneType, UnknownResponse, BinaryResponseContent
from ._utils import is_given
from ._models import BaseModel
from ._constants import RAW_RESPONSE_HEADER
Expand Down Expand Up @@ -135,6 +135,9 @@ def _parse(self) -> R:

origin = get_origin(cast_to) or cast_to

if inspect.isclass(origin) and issubclass(origin, BinaryResponseContent):
return cast(R, cast_to(response)) # type: ignore

if origin == APIResponse:
raise RuntimeError("Unexpected state - cast_to is `APIResponse`")

Expand Down
Loading