diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
new file mode 100644
index 0000000..44450e0
--- /dev/null
+++ b/.github/workflows/publish-pypi.yml
@@ -0,0 +1,31 @@
+# This workflow is triggered when a GitHub release is created.
+# It can also be run manually to re-publish to PyPI in case it failed for some reason.
+# You can run this workflow by navigating to https://www.github.com/unlayer/unlayer-python/actions/workflows/publish-pypi.yml
+name: Publish PyPI
+on:
+ workflow_dispatch:
+
+ release:
+ types: [published]
+
+jobs:
+ publish:
+ name: publish
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Rye
+ run: |
+ curl -sSf https://rye.astral.sh/get | bash
+ echo "$HOME/.rye/shims" >> $GITHUB_PATH
+ env:
+ RYE_VERSION: '0.44.0'
+ RYE_INSTALL_OPTION: '--yes'
+
+ - name: Publish to PyPI
+ run: |
+ bash ./bin/publish-pypi
+ env:
+ PYPI_TOKEN: ${{ secrets.UNLAYER_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
new file mode 100644
index 0000000..746dab3
--- /dev/null
+++ b/.github/workflows/release-doctor.yml
@@ -0,0 +1,21 @@
+name: Release Doctor
+on:
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ release_doctor:
+ name: release doctor
+ runs-on: ubuntu-latest
+ if: github.repository == 'unlayer/unlayer-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Check release environment
+ run: |
+ bash ./bin/check-release-environment
+ env:
+ PYPI_TOKEN: ${{ secrets.UNLAYER_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..3d2ac0b
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "0.1.0"
+}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index c827f62..2702d73 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 7
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/unlayer%2Funlayer-e77bc881d5cb6c68be6f3d3861ed021b99f6cde45ee28d3511abe284d888262e.yml
-openapi_spec_hash: 7e7fae1b919c5d337e8b22be2a24d3ed
-config_hash: ea554d62bfbb5e8ea43ebf0a274dec31
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/unlayer%2Funlayer-48f00d1c04c23fb4d1cb7cf4af4f56b0c920d758c1f06e06e5373e5b15e9c27d.yml
+openapi_spec_hash: 6ee2a94bb9840aceb4a6161c724ce46c
+config_hash: 249869757b6eb98ae3d58f2a47ce21e2
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3935a47
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,53 @@
+# Changelog
+
+## 0.1.0 (2026-02-24)
+
+Full Changelog: [v0.0.1...v0.1.0](https://github.com/unlayer/unlayer-python/compare/v0.0.1...v0.1.0)
+
+### Features
+
+* **api:** api update ([e247358](https://github.com/unlayer/unlayer-python/commit/e24735806fc790506d1d3b9ee84954809bd02258))
+* **api:** api update ([1255c9b](https://github.com/unlayer/unlayer-python/commit/1255c9b3e6bd708eefb21313c022a1018a78fc3e))
+* **api:** api update ([3901e44](https://github.com/unlayer/unlayer-python/commit/3901e44ec76d4bdc1ae8055639d1121965f8e6c6))
+* **api:** api update ([e90df1d](https://github.com/unlayer/unlayer-python/commit/e90df1d06e53f40707ccc73610d797eaa3c51fa5))
+* **api:** api update ([d0bb085](https://github.com/unlayer/unlayer-python/commit/d0bb0855866ba1daf54f700415c01fcf752090f3))
+* **api:** api update ([39253ba](https://github.com/unlayer/unlayer-python/commit/39253ba25f6a5da8e18da9d564af41e0c7e4c0e7))
+* **api:** api update ([b65e14e](https://github.com/unlayer/unlayer-python/commit/b65e14e7a19564d228e7436bae27b35a9ede6693))
+* **api:** api update ([d83034d](https://github.com/unlayer/unlayer-python/commit/d83034df2dbb4dfc3ee96d101bcdce87dc66306f))
+* **api:** api update ([93b319e](https://github.com/unlayer/unlayer-python/commit/93b319e21ac5f484c9d3efa4a51fba4269fe6737))
+* **api:** api update ([dd927cc](https://github.com/unlayer/unlayer-python/commit/dd927cc54abf0b619091f71e91d778da87323043))
+* **api:** api update ([1a2f183](https://github.com/unlayer/unlayer-python/commit/1a2f183e4fd9a9e512a41855aa1b91b4d28d6bdf))
+* **api:** api update ([1cf5d8e](https://github.com/unlayer/unlayer-python/commit/1cf5d8e9a10c2b8684838fa628b1b864bbe74209))
+* **api:** api update ([35876ba](https://github.com/unlayer/unlayer-python/commit/35876ba78ab506c86a36ae8aead33bf3f04549a6))
+* **api:** api update ([eafe372](https://github.com/unlayer/unlayer-python/commit/eafe372ab530d1a516b6f88630060dd1e384a288))
+* **api:** api update ([db50139](https://github.com/unlayer/unlayer-python/commit/db5013964e0c7efdc485708d622c12fde20c9420))
+* **api:** api update ([37dfa82](https://github.com/unlayer/unlayer-python/commit/37dfa8228ed4158855318afb16b50dc71c78b1bb))
+* **api:** api update ([04d7f14](https://github.com/unlayer/unlayer-python/commit/04d7f14ec06f71d777b7624b8898e3594dcb88f0))
+* **api:** api update ([7dbfb1a](https://github.com/unlayer/unlayer-python/commit/7dbfb1aa7fa765b1750f6ed5a733f4cf584c9766))
+* **api:** api update ([51ce8ef](https://github.com/unlayer/unlayer-python/commit/51ce8ef8927b0824ccd847e0acf61cd57388ae0f))
+* **client:** add custom JSON encoder for extended type support ([16d4717](https://github.com/unlayer/unlayer-python/commit/16d4717e1dec685c212a6c7d74e969660cf7e03c))
+* **client:** add support for binary request streaming ([191bd18](https://github.com/unlayer/unlayer-python/commit/191bd18a0c2588e3742b49b2bb85c23f31c18367))
+
+
+### Bug Fixes
+
+* use async_to_httpx_files in patch method ([f9bdb58](https://github.com/unlayer/unlayer-python/commit/f9bdb58b5c218d7310e8c3328fc79d2322bc7d24))
+
+
+### Chores
+
+* **ci:** upgrade `actions/github-script` ([89356cd](https://github.com/unlayer/unlayer-python/commit/89356cd10af2df4ce00e52b3857e772c2ffdda9f))
+* configure new SDK language ([9596a35](https://github.com/unlayer/unlayer-python/commit/9596a35093e7c1a7bfb9c1a9da7319482ed4f9c8))
+* format all `api.md` files ([eaab341](https://github.com/unlayer/unlayer-python/commit/eaab341586836de0b5d88ccd15ba91bf5d9fb8a9))
+* **internal:** add `--fix` argument to lint script ([3402766](https://github.com/unlayer/unlayer-python/commit/340276634acd12227076b5b3fbc3a1d5a8aaaf2f))
+* **internal:** add missing files argument to base client ([011c5b3](https://github.com/unlayer/unlayer-python/commit/011c5b3e1d285ad929aa23fbd18f22ded6e37bc2))
+* **internal:** add request options to SSE classes ([8215016](https://github.com/unlayer/unlayer-python/commit/82150166172718373d5c625fcb2abd63b996d1c4))
+* **internal:** bump dependencies ([4796143](https://github.com/unlayer/unlayer-python/commit/47961438c81e3263207987628d1f00c04a117189))
+* **internal:** codegen related update ([d4e8db7](https://github.com/unlayer/unlayer-python/commit/d4e8db7de852054c9232097b80832e630383da0e))
+* **internal:** fix lint error on Python 3.14 ([e825b41](https://github.com/unlayer/unlayer-python/commit/e825b41fa160ab18110a59b28ffdf3c504d63407))
+* **internal:** make `test_proxy_environment_variables` more resilient ([17a5609](https://github.com/unlayer/unlayer-python/commit/17a5609325e6661d12910c96b3f41ecde33e5cf1))
+* **internal:** update `actions/checkout` version ([942160b](https://github.com/unlayer/unlayer-python/commit/942160b8a331a9d21fe2349fba265fd6fb520034))
+* speedup initial import ([a0429e9](https://github.com/unlayer/unlayer-python/commit/a0429e971dcf003df2bbfd2d523bea4aab46db76))
+* update mock server docs ([e835dc3](https://github.com/unlayer/unlayer-python/commit/e835dc37eab896b138333888f423ba0aae6f0958))
+* update SDK settings ([52e5d05](https://github.com/unlayer/unlayer-python/commit/52e5d054a25723d3798cd8a571cd48bc7625bce9))
+* update SDK settings ([63ab6f7](https://github.com/unlayer/unlayer-python/commit/63ab6f7d4fee33593fe9d3dfb6daa615bc00897e))
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8bc8eee..8f8a253 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -62,7 +62,7 @@ If you’d like to use the repository from source, you can either install from g
To install via git:
```sh
-$ pip install git+ssh://git@github.com/stainless-sdks/unlayer-python.git
+$ pip install git+ssh://git@github.com/unlayer/unlayer-python.git
```
Alternatively, you can build from source and install the wheel file:
@@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
```sh
-# you will need npm installed
-$ npx prism mock path/to/your/openapi.yml
+$ ./scripts/mock
```
```sh
@@ -120,7 +119,7 @@ the changes aren't made through the automated pipeline, you may want to make rel
### Publish with a GitHub workflow
-You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/stainless-sdks/unlayer-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.
+You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/unlayer/unlayer-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.
### Publish manually
diff --git a/README.md b/README.md
index 8e7620c..f4c8537 100644
--- a/README.md
+++ b/README.md
@@ -16,13 +16,10 @@ The full API of this library can be found in [api.md](api.md).
## Installation
```sh
-# install from this staging repo
-pip install git+ssh://git@github.com/stainless-sdks/unlayer-python.git
+# install from PyPI
+pip install unlayer
```
-> [!NOTE]
-> Once this package is [published to PyPI](https://www.stainless.com/docs/guides/publish), this will become: `pip install unlayer`
-
## Usage
The full API of this library can be found in [api.md](api.md).
@@ -32,21 +29,20 @@ import os
from unlayer import Unlayer
client = Unlayer(
- access_token=os.environ.get("UNLAYER_ACCESS_TOKEN"), # This is the default and can be omitted
- # or 'production' | 'qa' | 'dev'; defaults to "production".
- environment="stage",
+ api_key=os.environ.get("UNLAYER_API_KEY"), # This is the default and can be omitted
)
-project = client.project.retrieve(
+page = client.templates.list(
+ limit=10,
project_id="your-project-id",
)
-print(project.data)
+print(page.data)
```
-While you can provide a `access_token` keyword argument,
+While you can provide an `api_key` keyword argument,
we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
-to add `UNLAYER_ACCESS_TOKEN="My Access Token"` to your `.env` file
-so that your Access Token is not stored in source control.
+to add `UNLAYER_API_KEY="My API Key"` to your `.env` file
+so that your API Key is not stored in source control.
## Async usage
@@ -58,17 +54,16 @@ import asyncio
from unlayer import AsyncUnlayer
client = AsyncUnlayer(
- access_token=os.environ.get("UNLAYER_ACCESS_TOKEN"), # This is the default and can be omitted
- # or 'production' | 'qa' | 'dev'; defaults to "production".
- environment="stage",
+ api_key=os.environ.get("UNLAYER_API_KEY"), # This is the default and can be omitted
)
async def main() -> None:
- project = await client.project.retrieve(
+ page = await client.templates.list(
+ limit=10,
project_id="your-project-id",
)
- print(project.data)
+ print(page.data)
asyncio.run(main())
@@ -83,8 +78,8 @@ By default, the async client uses `httpx` for HTTP requests. However, for improv
You can enable this by installing `aiohttp`:
```sh
-# install from this staging repo
-pip install 'unlayer[aiohttp] @ git+ssh://git@github.com/stainless-sdks/unlayer-python.git'
+# install from PyPI
+pip install unlayer[aiohttp]
```
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
@@ -98,15 +93,14 @@ from unlayer import AsyncUnlayer
async def main() -> None:
async with AsyncUnlayer(
- access_token=os.environ.get(
- "UNLAYER_ACCESS_TOKEN"
- ), # This is the default and can be omitted
+ api_key=os.environ.get("UNLAYER_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
- project = await client.project.retrieve(
+ page = await client.templates.list(
+ limit=10,
project_id="your-project-id",
)
- print(project.data)
+ print(page.data)
asyncio.run(main())
@@ -135,8 +129,8 @@ client = Unlayer()
all_templates = []
# Automatically fetches more pages as needed.
for template in client.templates.list(
- project_id="your-project-id",
limit=10,
+ project_id="your-project-id",
):
# Do something with template here
all_templates.append(template)
@@ -156,8 +150,8 @@ async def main() -> None:
all_templates = []
# Iterate through items across all pages, issuing requests as needed.
async for template in client.templates.list(
- project_id="your-project-id",
limit=10,
+ project_id="your-project-id",
):
all_templates.append(template)
print(all_templates)
@@ -170,8 +164,8 @@ Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get
```python
first_page = await client.templates.list(
- project_id="your-project-id",
limit=10,
+ project_id="your-project-id",
)
if first_page.has_next_page():
print(f"will fetch next page using these details: {first_page.next_page_info()}")
@@ -185,8 +179,8 @@ Or just work directly with the returned data:
```python
first_page = await client.templates.list(
- project_id="your-project-id",
limit=10,
+ project_id="your-project-id",
)
print(f"next page cursor: {first_page.next_cursor}") # => "next page cursor: ..."
@@ -227,7 +221,8 @@ from unlayer import Unlayer
client = Unlayer()
try:
- client.project.retrieve(
+ client.templates.list(
+ limit=10,
project_id="your-project-id",
)
except unlayer.APIConnectionError as e:
@@ -272,7 +267,8 @@ client = Unlayer(
)
# Or, configure per-request:
-client.with_options(max_retries=5).project.retrieve(
+client.with_options(max_retries=5).templates.list(
+ limit=10,
project_id="your-project-id",
)
```
@@ -297,7 +293,8 @@ client = Unlayer(
)
# Override per-request:
-client.with_options(timeout=5.0).project.retrieve(
+client.with_options(timeout=5.0).templates.list(
+ limit=10,
project_id="your-project-id",
)
```
@@ -340,18 +337,19 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to
from unlayer import Unlayer
client = Unlayer()
-response = client.project.with_raw_response.retrieve(
+response = client.templates.with_raw_response.list(
+ limit=10,
project_id="your-project-id",
)
print(response.headers.get('X-My-Header'))
-project = response.parse() # get the object that `project.retrieve()` would have returned
-print(project.data)
+template = response.parse() # get the object that `templates.list()` would have returned
+print(template.id)
```
-These methods return an [`APIResponse`](https://github.com/stainless-sdks/unlayer-python/tree/main/src/unlayer/_response.py) object.
+These methods return an [`APIResponse`](https://github.com/unlayer/unlayer-python/tree/main/src/unlayer/_response.py) object.
-The async client returns an [`AsyncAPIResponse`](https://github.com/stainless-sdks/unlayer-python/tree/main/src/unlayer/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
+The async client returns an [`AsyncAPIResponse`](https://github.com/unlayer/unlayer-python/tree/main/src/unlayer/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
#### `.with_streaming_response`
@@ -360,7 +358,8 @@ The above interface eagerly reads the full response body when you make the reque
To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.
```python
-with client.project.with_streaming_response.retrieve(
+with client.templates.with_streaming_response.list(
+ limit=10,
project_id="your-project-id",
) as response:
print(response.headers.get("X-My-Header"))
@@ -457,7 +456,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con
We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
-We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/unlayer-python/issues) with questions, bugs, or suggestions.
+We are keen for your feedback; please open an [issue](https://www.github.com/unlayer/unlayer-python/issues) with questions, bugs, or suggestions.
### Determining the installed version
diff --git a/api.md b/api.md
index 54f0d00..4166417 100644
--- a/api.md
+++ b/api.md
@@ -24,7 +24,7 @@ Methods:
- client.convert.simple_to_full.create(\*\*params) -> SimpleToFullCreateResponse
-# Project
+# Projects
Types:
@@ -34,7 +34,7 @@ from unlayer.types import ProjectRetrieveResponse
Methods:
-- client.project.retrieve(\*\*params) -> ProjectRetrieveResponse
+- client.projects.retrieve(id) -> ProjectRetrieveResponse
# Templates
diff --git a/bin/check-release-environment b/bin/check-release-environment
new file mode 100644
index 0000000..b845b0f
--- /dev/null
+++ b/bin/check-release-environment
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+errors=()
+
+if [ -z "${PYPI_TOKEN}" ]; then
+ errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.")
+fi
+
+lenErrors=${#errors[@]}
+
+if [[ lenErrors -gt 0 ]]; then
+ echo -e "Found the following errors in the release environment:\n"
+
+ for error in "${errors[@]}"; do
+ echo -e "- $error\n"
+ done
+
+ exit 1
+fi
+
+echo "The environment is ready to push releases!"
diff --git a/pyproject.toml b/pyproject.toml
index 2aa32b4..cbb8b33 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "unlayer"
-version = "0.0.1"
+version = "0.1.0"
description = "The official Python library for the unlayer API"
dynamic = ["readme"]
license = "Apache-2.0"
@@ -37,8 +37,8 @@ classifiers = [
]
[project.urls]
-Homepage = "https://github.com/stainless-sdks/unlayer-python"
-Repository = "https://github.com/stainless-sdks/unlayer-python"
+Homepage = "https://github.com/unlayer/unlayer-python"
+Repository = "https://github.com/unlayer/unlayer-python"
[project.optional-dependencies]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
@@ -126,7 +126,7 @@ path = "README.md"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
# replace relative links with absolute links
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
-replacement = '[\1](https://github.com/stainless-sdks/unlayer-python/tree/main/\g<2>)'
+replacement = '[\1](https://github.com/unlayer/unlayer-python/tree/main/\g<2>)'
[tool.pytest.ini_options]
testpaths = ["tests"]
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..d1ec05c
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,66 @@
+{
+ "packages": {
+ ".": {}
+ },
+ "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json",
+ "include-v-in-tag": true,
+ "include-component-in-tag": false,
+ "versioning": "prerelease",
+ "prerelease": true,
+ "bump-minor-pre-major": true,
+ "bump-patch-for-minor-pre-major": false,
+ "pull-request-header": "Automated Release PR",
+ "pull-request-title-pattern": "release: ${version}",
+ "changelog-sections": [
+ {
+ "type": "feat",
+ "section": "Features"
+ },
+ {
+ "type": "fix",
+ "section": "Bug Fixes"
+ },
+ {
+ "type": "perf",
+ "section": "Performance Improvements"
+ },
+ {
+ "type": "revert",
+ "section": "Reverts"
+ },
+ {
+ "type": "chore",
+ "section": "Chores"
+ },
+ {
+ "type": "docs",
+ "section": "Documentation"
+ },
+ {
+ "type": "style",
+ "section": "Styles"
+ },
+ {
+ "type": "refactor",
+ "section": "Refactors"
+ },
+ {
+ "type": "test",
+ "section": "Tests",
+ "hidden": true
+ },
+ {
+ "type": "build",
+ "section": "Build System"
+ },
+ {
+ "type": "ci",
+ "section": "Continuous Integration",
+ "hidden": true
+ }
+ ],
+ "release-type": "python",
+ "extra-files": [
+ "src/unlayer/_version.py"
+ ]
+}
\ No newline at end of file
diff --git a/src/unlayer/__init__.py b/src/unlayer/__init__.py
index 196b04d..37d9b74 100644
--- a/src/unlayer/__init__.py
+++ b/src/unlayer/__init__.py
@@ -5,18 +5,7 @@
from . import types
from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
from ._utils import file_from_path
-from ._client import (
- ENVIRONMENTS,
- Client,
- Stream,
- Timeout,
- Unlayer,
- Transport,
- AsyncClient,
- AsyncStream,
- AsyncUnlayer,
- RequestOptions,
-)
+from ._client import Client, Stream, Timeout, Unlayer, Transport, AsyncClient, AsyncStream, AsyncUnlayer, RequestOptions
from ._models import BaseModel
from ._version import __title__, __version__
from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
@@ -74,7 +63,6 @@
"AsyncStream",
"Unlayer",
"AsyncUnlayer",
- "ENVIRONMENTS",
"file_from_path",
"BaseModel",
"DEFAULT_TIMEOUT",
diff --git a/src/unlayer/_client.py b/src/unlayer/_client.py
index 01b60d9..08f7fef 100644
--- a/src/unlayer/_client.py
+++ b/src/unlayer/_client.py
@@ -3,8 +3,8 @@
from __future__ import annotations
import os
-from typing import TYPE_CHECKING, Any, Dict, Mapping, cast
-from typing_extensions import Self, Literal, override
+from typing import TYPE_CHECKING, Any, Mapping
+from typing_extensions import Self, override
import httpx
@@ -12,6 +12,7 @@
from ._qs import Querystring
from ._types import (
Omit,
+ Headers,
Timeout,
NotGiven,
Transport,
@@ -23,7 +24,7 @@
from ._compat import cached_property
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
-from ._exceptions import UnlayerError, APIStatusError
+from ._exceptions import APIStatusError
from ._base_client import (
DEFAULT_MAX_RETRIES,
SyncAPIClient,
@@ -31,44 +32,28 @@
)
if TYPE_CHECKING:
- from .resources import convert, project, templates, workspaces
- from .resources.project import ProjectResource, AsyncProjectResource
+ from .resources import convert, projects, templates, workspaces
+ from .resources.projects import ProjectsResource, AsyncProjectsResource
from .resources.templates import TemplatesResource, AsyncTemplatesResource
from .resources.workspaces import WorkspacesResource, AsyncWorkspacesResource
from .resources.convert.convert import ConvertResource, AsyncConvertResource
-__all__ = [
- "ENVIRONMENTS",
- "Timeout",
- "Transport",
- "ProxiesTypes",
- "RequestOptions",
- "Unlayer",
- "AsyncUnlayer",
- "Client",
- "AsyncClient",
-]
-
-ENVIRONMENTS: Dict[str, str] = {
- "production": "https://api.unlayer.com",
- "stage": "https://api.stage.unlayer.com",
- "qa": "https://api.qa.unlayer.com",
- "dev": "https://api.dev.unlayer.com",
-}
+__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Unlayer", "AsyncUnlayer", "Client", "AsyncClient"]
class Unlayer(SyncAPIClient):
# client options
- access_token: str
-
- _environment: Literal["production", "stage", "qa", "dev"] | NotGiven
+ api_key: str | None
+ personal_access_token: str | None
+ project_id: str | None
def __init__(
self,
*,
- access_token: str | None = None,
- environment: Literal["production", "stage", "qa", "dev"] | NotGiven = not_given,
- base_url: str | httpx.URL | None | NotGiven = not_given,
+ api_key: str | None = None,
+ personal_access_token: str | None = None,
+ project_id: str | None = None,
+ base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = not_given,
max_retries: int = DEFAULT_MAX_RETRIES,
default_headers: Mapping[str, str] | None = None,
@@ -89,41 +74,27 @@ def __init__(
) -> None:
"""Construct a new synchronous Unlayer client instance.
- This automatically infers the `access_token` argument from the `UNLAYER_ACCESS_TOKEN` environment variable if it is not provided.
+ This automatically infers the following arguments from their corresponding environment variables if they are not provided:
+ - `api_key` from `UNLAYER_API_KEY`
+ - `personal_access_token` from `UNLAYER_PERSONAL_ACCESS_TOKEN`
+ - `project_id` from `UNLAYER_PROJECT_ID`
"""
- if access_token is None:
- access_token = os.environ.get("UNLAYER_ACCESS_TOKEN")
- if access_token is None:
- raise UnlayerError(
- "The access_token client option must be set either by passing access_token to the client or by setting the UNLAYER_ACCESS_TOKEN environment variable"
- )
- self.access_token = access_token
-
- self._environment = environment
-
- base_url_env = os.environ.get("UNLAYER_BASE_URL")
- if is_given(base_url) and base_url is not None:
- # cast required because mypy doesn't understand the type narrowing
- base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast]
- elif is_given(environment):
- if base_url_env and base_url is not None:
- raise ValueError(
- "Ambiguous URL; The `UNLAYER_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
- )
-
- try:
- base_url = ENVIRONMENTS[environment]
- except KeyError as exc:
- raise ValueError(f"Unknown environment: {environment}") from exc
- elif base_url_env is not None:
- base_url = base_url_env
- else:
- self._environment = environment = "production"
-
- try:
- base_url = ENVIRONMENTS[environment]
- except KeyError as exc:
- raise ValueError(f"Unknown environment: {environment}") from exc
+ if api_key is None:
+ api_key = os.environ.get("UNLAYER_API_KEY")
+ self.api_key = api_key
+
+ if personal_access_token is None:
+ personal_access_token = os.environ.get("UNLAYER_PERSONAL_ACCESS_TOKEN")
+ self.personal_access_token = personal_access_token
+
+ if project_id is None:
+ project_id = os.environ.get("UNLAYER_PROJECT_ID")
+ self.project_id = project_id
+
+ if base_url is None:
+ base_url = os.environ.get("UNLAYER_BASE_URL")
+ if base_url is None:
+ base_url = f"https://api.unlayer.com"
super().__init__(
version=__version__,
@@ -143,10 +114,10 @@ def convert(self) -> ConvertResource:
return ConvertResource(self)
@cached_property
- def project(self) -> ProjectResource:
- from .resources.project import ProjectResource
+ def projects(self) -> ProjectsResource:
+ from .resources.projects import ProjectsResource
- return ProjectResource(self)
+ return ProjectsResource(self)
@cached_property
def templates(self) -> TemplatesResource:
@@ -176,8 +147,21 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- access_token = self.access_token
- return {"Authorization": f"Bearer {access_token}"}
+ return {**self._api_key_auth, **self._personal_access_token_auth}
+
+ @property
+ def _api_key_auth(self) -> dict[str, str]:
+ api_key = self.api_key
+ if api_key is None:
+ return {}
+ return {"Authorization": f"Bearer {api_key}"}
+
+ @property
+ def _personal_access_token_auth(self) -> dict[str, str]:
+ personal_access_token = self.personal_access_token
+ if personal_access_token is None:
+ return {}
+ return {"Authorization": f"Bearer {personal_access_token}"}
@property
@override
@@ -185,14 +169,25 @@ def default_headers(self) -> dict[str, str | Omit]:
return {
**super().default_headers,
"X-Stainless-Async": "false",
+ "X-Project-ID": self.project_id if self.project_id is not None else Omit(),
**self._custom_headers,
}
+ @override
+ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+ if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
+ return
+
+ raise TypeError(
+ '"Could not resolve authentication method. Expected either api_key or personal_access_token to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted"'
+ )
+
def copy(
self,
*,
- access_token: str | None = None,
- environment: Literal["production", "stage", "qa", "dev"] | None = None,
+ api_key: str | None = None,
+ personal_access_token: str | None = None,
+ project_id: str | None = None,
base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = not_given,
http_client: httpx.Client | None = None,
@@ -226,9 +221,10 @@ def copy(
http_client = http_client or self._client
return self.__class__(
- access_token=access_token or self.access_token,
+ api_key=api_key or self.api_key,
+ personal_access_token=personal_access_token or self.personal_access_token,
+ project_id=project_id or self.project_id,
base_url=base_url or self.base_url,
- environment=environment or self._environment,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
http_client=http_client,
max_retries=max_retries if is_given(max_retries) else self.max_retries,
@@ -277,16 +273,17 @@ def _make_status_error(
class AsyncUnlayer(AsyncAPIClient):
# client options
- access_token: str
-
- _environment: Literal["production", "stage", "qa", "dev"] | NotGiven
+ api_key: str | None
+ personal_access_token: str | None
+ project_id: str | None
def __init__(
self,
*,
- access_token: str | None = None,
- environment: Literal["production", "stage", "qa", "dev"] | NotGiven = not_given,
- base_url: str | httpx.URL | None | NotGiven = not_given,
+ api_key: str | None = None,
+ personal_access_token: str | None = None,
+ project_id: str | None = None,
+ base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = not_given,
max_retries: int = DEFAULT_MAX_RETRIES,
default_headers: Mapping[str, str] | None = None,
@@ -307,41 +304,27 @@ def __init__(
) -> None:
"""Construct a new async AsyncUnlayer client instance.
- This automatically infers the `access_token` argument from the `UNLAYER_ACCESS_TOKEN` environment variable if it is not provided.
+ This automatically infers the following arguments from their corresponding environment variables if they are not provided:
+ - `api_key` from `UNLAYER_API_KEY`
+ - `personal_access_token` from `UNLAYER_PERSONAL_ACCESS_TOKEN`
+ - `project_id` from `UNLAYER_PROJECT_ID`
"""
- if access_token is None:
- access_token = os.environ.get("UNLAYER_ACCESS_TOKEN")
- if access_token is None:
- raise UnlayerError(
- "The access_token client option must be set either by passing access_token to the client or by setting the UNLAYER_ACCESS_TOKEN environment variable"
- )
- self.access_token = access_token
-
- self._environment = environment
-
- base_url_env = os.environ.get("UNLAYER_BASE_URL")
- if is_given(base_url) and base_url is not None:
- # cast required because mypy doesn't understand the type narrowing
- base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast]
- elif is_given(environment):
- if base_url_env and base_url is not None:
- raise ValueError(
- "Ambiguous URL; The `UNLAYER_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
- )
-
- try:
- base_url = ENVIRONMENTS[environment]
- except KeyError as exc:
- raise ValueError(f"Unknown environment: {environment}") from exc
- elif base_url_env is not None:
- base_url = base_url_env
- else:
- self._environment = environment = "production"
-
- try:
- base_url = ENVIRONMENTS[environment]
- except KeyError as exc:
- raise ValueError(f"Unknown environment: {environment}") from exc
+ if api_key is None:
+ api_key = os.environ.get("UNLAYER_API_KEY")
+ self.api_key = api_key
+
+ if personal_access_token is None:
+ personal_access_token = os.environ.get("UNLAYER_PERSONAL_ACCESS_TOKEN")
+ self.personal_access_token = personal_access_token
+
+ if project_id is None:
+ project_id = os.environ.get("UNLAYER_PROJECT_ID")
+ self.project_id = project_id
+
+ if base_url is None:
+ base_url = os.environ.get("UNLAYER_BASE_URL")
+ if base_url is None:
+ base_url = f"https://api.unlayer.com"
super().__init__(
version=__version__,
@@ -361,10 +344,10 @@ def convert(self) -> AsyncConvertResource:
return AsyncConvertResource(self)
@cached_property
- def project(self) -> AsyncProjectResource:
- from .resources.project import AsyncProjectResource
+ def projects(self) -> AsyncProjectsResource:
+ from .resources.projects import AsyncProjectsResource
- return AsyncProjectResource(self)
+ return AsyncProjectsResource(self)
@cached_property
def templates(self) -> AsyncTemplatesResource:
@@ -394,8 +377,21 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- access_token = self.access_token
- return {"Authorization": f"Bearer {access_token}"}
+ return {**self._api_key_auth, **self._personal_access_token_auth}
+
+ @property
+ def _api_key_auth(self) -> dict[str, str]:
+ api_key = self.api_key
+ if api_key is None:
+ return {}
+ return {"Authorization": f"Bearer {api_key}"}
+
+ @property
+ def _personal_access_token_auth(self) -> dict[str, str]:
+ personal_access_token = self.personal_access_token
+ if personal_access_token is None:
+ return {}
+ return {"Authorization": f"Bearer {personal_access_token}"}
@property
@override
@@ -403,14 +399,25 @@ def default_headers(self) -> dict[str, str | Omit]:
return {
**super().default_headers,
"X-Stainless-Async": f"async:{get_async_library()}",
+ "X-Project-ID": self.project_id if self.project_id is not None else Omit(),
**self._custom_headers,
}
+ @override
+ def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
+ if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
+ return
+
+ raise TypeError(
+ '"Could not resolve authentication method. Expected either api_key or personal_access_token to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted"'
+ )
+
def copy(
self,
*,
- access_token: str | None = None,
- environment: Literal["production", "stage", "qa", "dev"] | None = None,
+ api_key: str | None = None,
+ personal_access_token: str | None = None,
+ project_id: str | None = None,
base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = not_given,
http_client: httpx.AsyncClient | None = None,
@@ -444,9 +451,10 @@ def copy(
http_client = http_client or self._client
return self.__class__(
- access_token=access_token or self.access_token,
+ api_key=api_key or self.api_key,
+ personal_access_token=personal_access_token or self.personal_access_token,
+ project_id=project_id or self.project_id,
base_url=base_url or self.base_url,
- environment=environment or self._environment,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
http_client=http_client,
max_retries=max_retries if is_given(max_retries) else self.max_retries,
@@ -506,10 +514,10 @@ def convert(self) -> convert.ConvertResourceWithRawResponse:
return ConvertResourceWithRawResponse(self._client.convert)
@cached_property
- def project(self) -> project.ProjectResourceWithRawResponse:
- from .resources.project import ProjectResourceWithRawResponse
+ def projects(self) -> projects.ProjectsResourceWithRawResponse:
+ from .resources.projects import ProjectsResourceWithRawResponse
- return ProjectResourceWithRawResponse(self._client.project)
+ return ProjectsResourceWithRawResponse(self._client.projects)
@cached_property
def templates(self) -> templates.TemplatesResourceWithRawResponse:
@@ -537,10 +545,10 @@ def convert(self) -> convert.AsyncConvertResourceWithRawResponse:
return AsyncConvertResourceWithRawResponse(self._client.convert)
@cached_property
- def project(self) -> project.AsyncProjectResourceWithRawResponse:
- from .resources.project import AsyncProjectResourceWithRawResponse
+ def projects(self) -> projects.AsyncProjectsResourceWithRawResponse:
+ from .resources.projects import AsyncProjectsResourceWithRawResponse
- return AsyncProjectResourceWithRawResponse(self._client.project)
+ return AsyncProjectsResourceWithRawResponse(self._client.projects)
@cached_property
def templates(self) -> templates.AsyncTemplatesResourceWithRawResponse:
@@ -568,10 +576,10 @@ def convert(self) -> convert.ConvertResourceWithStreamingResponse:
return ConvertResourceWithStreamingResponse(self._client.convert)
@cached_property
- def project(self) -> project.ProjectResourceWithStreamingResponse:
- from .resources.project import ProjectResourceWithStreamingResponse
+ def projects(self) -> projects.ProjectsResourceWithStreamingResponse:
+ from .resources.projects import ProjectsResourceWithStreamingResponse
- return ProjectResourceWithStreamingResponse(self._client.project)
+ return ProjectsResourceWithStreamingResponse(self._client.projects)
@cached_property
def templates(self) -> templates.TemplatesResourceWithStreamingResponse:
@@ -599,10 +607,10 @@ def convert(self) -> convert.AsyncConvertResourceWithStreamingResponse:
return AsyncConvertResourceWithStreamingResponse(self._client.convert)
@cached_property
- def project(self) -> project.AsyncProjectResourceWithStreamingResponse:
- from .resources.project import AsyncProjectResourceWithStreamingResponse
+ def projects(self) -> projects.AsyncProjectsResourceWithStreamingResponse:
+ from .resources.projects import AsyncProjectsResourceWithStreamingResponse
- return AsyncProjectResourceWithStreamingResponse(self._client.project)
+ return AsyncProjectsResourceWithStreamingResponse(self._client.projects)
@cached_property
def templates(self) -> templates.AsyncTemplatesResourceWithStreamingResponse:
diff --git a/src/unlayer/_response.py b/src/unlayer/_response.py
index 9a45370..c26d661 100644
--- a/src/unlayer/_response.py
+++ b/src/unlayer/_response.py
@@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
),
response=self.http_response,
client=cast(Any, self._client),
+ options=self._options,
),
)
@@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
cast_to=extract_stream_chunk_type(self._stream_cls),
response=self.http_response,
client=cast(Any, self._client),
+ options=self._options,
),
)
@@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
cast_to=cast_to,
response=self.http_response,
client=cast(Any, self._client),
+ options=self._options,
),
)
diff --git a/src/unlayer/_streaming.py b/src/unlayer/_streaming.py
index d6a6f4d..097da18 100644
--- a/src/unlayer/_streaming.py
+++ b/src/unlayer/_streaming.py
@@ -4,7 +4,7 @@
import json
import inspect
from types import TracebackType
-from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast
from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable
import httpx
@@ -13,6 +13,7 @@
if TYPE_CHECKING:
from ._client import Unlayer, AsyncUnlayer
+ from ._models import FinalRequestOptions
_T = TypeVar("_T")
@@ -22,7 +23,7 @@ class Stream(Generic[_T]):
"""Provides the core interface to iterate over a synchronous stream response."""
response: httpx.Response
-
+ _options: Optional[FinalRequestOptions] = None
_decoder: SSEBytesDecoder
def __init__(
@@ -31,10 +32,12 @@ def __init__(
cast_to: type[_T],
response: httpx.Response,
client: Unlayer,
+ options: Optional[FinalRequestOptions] = None,
) -> None:
self.response = response
self._cast_to = cast_to
self._client = client
+ self._options = options
self._decoder = client._make_sse_decoder()
self._iterator = self.__stream__()
@@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]):
"""Provides the core interface to iterate over an asynchronous stream response."""
response: httpx.Response
-
+ _options: Optional[FinalRequestOptions] = None
_decoder: SSEDecoder | SSEBytesDecoder
def __init__(
@@ -94,10 +97,12 @@ def __init__(
cast_to: type[_T],
response: httpx.Response,
client: AsyncUnlayer,
+ options: Optional[FinalRequestOptions] = None,
) -> None:
self.response = response
self._cast_to = cast_to
self._client = client
+ self._options = options
self._decoder = client._make_sse_decoder()
self._iterator = self.__stream__()
diff --git a/src/unlayer/_version.py b/src/unlayer/_version.py
index c9e1c88..47398ca 100644
--- a/src/unlayer/_version.py
+++ b/src/unlayer/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "unlayer"
-__version__ = "0.0.1"
+__version__ = "0.1.0" # x-release-please-version
diff --git a/src/unlayer/resources/__init__.py b/src/unlayer/resources/__init__.py
index 347e4de..1de5370 100644
--- a/src/unlayer/resources/__init__.py
+++ b/src/unlayer/resources/__init__.py
@@ -8,13 +8,13 @@
ConvertResourceWithStreamingResponse,
AsyncConvertResourceWithStreamingResponse,
)
-from .project import (
- ProjectResource,
- AsyncProjectResource,
- ProjectResourceWithRawResponse,
- AsyncProjectResourceWithRawResponse,
- ProjectResourceWithStreamingResponse,
- AsyncProjectResourceWithStreamingResponse,
+from .projects import (
+ ProjectsResource,
+ AsyncProjectsResource,
+ ProjectsResourceWithRawResponse,
+ AsyncProjectsResourceWithRawResponse,
+ ProjectsResourceWithStreamingResponse,
+ AsyncProjectsResourceWithStreamingResponse,
)
from .templates import (
TemplatesResource,
@@ -40,12 +40,12 @@
"AsyncConvertResourceWithRawResponse",
"ConvertResourceWithStreamingResponse",
"AsyncConvertResourceWithStreamingResponse",
- "ProjectResource",
- "AsyncProjectResource",
- "ProjectResourceWithRawResponse",
- "AsyncProjectResourceWithRawResponse",
- "ProjectResourceWithStreamingResponse",
- "AsyncProjectResourceWithStreamingResponse",
+ "ProjectsResource",
+ "AsyncProjectsResource",
+ "ProjectsResourceWithRawResponse",
+ "AsyncProjectsResourceWithRawResponse",
+ "ProjectsResourceWithStreamingResponse",
+ "AsyncProjectsResourceWithStreamingResponse",
"TemplatesResource",
"AsyncTemplatesResource",
"TemplatesResourceWithRawResponse",
diff --git a/src/unlayer/resources/convert/convert.py b/src/unlayer/resources/convert/convert.py
index c303200..08eee7c 100644
--- a/src/unlayer/resources/convert/convert.py
+++ b/src/unlayer/resources/convert/convert.py
@@ -39,7 +39,7 @@ def with_raw_response(self) -> ConvertResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return ConvertResourceWithRawResponse(self)
@@ -48,7 +48,7 @@ def with_streaming_response(self) -> ConvertResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return ConvertResourceWithStreamingResponse(self)
@@ -68,7 +68,7 @@ def with_raw_response(self) -> AsyncConvertResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return AsyncConvertResourceWithRawResponse(self)
@@ -77,7 +77,7 @@ def with_streaming_response(self) -> AsyncConvertResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return AsyncConvertResourceWithStreamingResponse(self)
diff --git a/src/unlayer/resources/convert/full_to_simple.py b/src/unlayer/resources/convert/full_to_simple.py
index 0d793c7..d5901f4 100644
--- a/src/unlayer/resources/convert/full_to_simple.py
+++ b/src/unlayer/resources/convert/full_to_simple.py
@@ -30,7 +30,7 @@ def with_raw_response(self) -> FullToSimpleResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return FullToSimpleResourceWithRawResponse(self)
@@ -39,7 +39,7 @@ def with_streaming_response(self) -> FullToSimpleResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return FullToSimpleResourceWithStreamingResponse(self)
@@ -97,7 +97,7 @@ def with_raw_response(self) -> AsyncFullToSimpleResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return AsyncFullToSimpleResourceWithRawResponse(self)
@@ -106,7 +106,7 @@ def with_streaming_response(self) -> AsyncFullToSimpleResourceWithStreamingRespo
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return AsyncFullToSimpleResourceWithStreamingResponse(self)
diff --git a/src/unlayer/resources/convert/simple_to_full.py b/src/unlayer/resources/convert/simple_to_full.py
index 29db149..d9b634f 100644
--- a/src/unlayer/resources/convert/simple_to_full.py
+++ b/src/unlayer/resources/convert/simple_to_full.py
@@ -30,7 +30,7 @@ def with_raw_response(self) -> SimpleToFullResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return SimpleToFullResourceWithRawResponse(self)
@@ -39,7 +39,7 @@ def with_streaming_response(self) -> SimpleToFullResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return SimpleToFullResourceWithStreamingResponse(self)
@@ -92,7 +92,7 @@ def with_raw_response(self) -> AsyncSimpleToFullResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return AsyncSimpleToFullResourceWithRawResponse(self)
@@ -101,7 +101,7 @@ def with_streaming_response(self) -> AsyncSimpleToFullResourceWithStreamingRespo
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return AsyncSimpleToFullResourceWithStreamingResponse(self)
diff --git a/src/unlayer/resources/project.py b/src/unlayer/resources/projects.py
similarity index 55%
rename from src/unlayer/resources/project.py
rename to src/unlayer/resources/projects.py
index 6ff7918..59073ac 100644
--- a/src/unlayer/resources/project.py
+++ b/src/unlayer/resources/projects.py
@@ -4,9 +4,7 @@
import httpx
-from ..types import project_retrieve_params
from .._types import Body, Query, Headers, NotGiven, not_given
-from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
@@ -18,33 +16,33 @@
from .._base_client import make_request_options
from ..types.project_retrieve_response import ProjectRetrieveResponse
-__all__ = ["ProjectResource", "AsyncProjectResource"]
+__all__ = ["ProjectsResource", "AsyncProjectsResource"]
-class ProjectResource(SyncAPIResource):
+class ProjectsResource(SyncAPIResource):
@cached_property
- def with_raw_response(self) -> ProjectResourceWithRawResponse:
+ def with_raw_response(self) -> ProjectsResourceWithRawResponse:
"""
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
- return ProjectResourceWithRawResponse(self)
+ return ProjectsResourceWithRawResponse(self)
@cached_property
- def with_streaming_response(self) -> ProjectResourceWithStreamingResponse:
+ def with_streaming_response(self) -> ProjectsResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
- return ProjectResourceWithStreamingResponse(self)
+ return ProjectsResourceWithStreamingResponse(self)
def retrieve(
self,
+ id: str,
*,
- project_id: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -53,11 +51,9 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProjectRetrieveResponse:
"""
- Get project details for the specified project.
+ Get project details by ID.
Args:
- project_id: The project ID
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -66,43 +62,41 @@ def retrieve(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._get(
- "/v3/project",
+ f"/v3/projects/{id}",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=maybe_transform({"project_id": project_id}, project_retrieve_params.ProjectRetrieveParams),
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=ProjectRetrieveResponse,
)
-class AsyncProjectResource(AsyncAPIResource):
+class AsyncProjectsResource(AsyncAPIResource):
@cached_property
- def with_raw_response(self) -> AsyncProjectResourceWithRawResponse:
+ def with_raw_response(self) -> AsyncProjectsResourceWithRawResponse:
"""
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
- return AsyncProjectResourceWithRawResponse(self)
+ return AsyncProjectsResourceWithRawResponse(self)
@cached_property
- def with_streaming_response(self) -> AsyncProjectResourceWithStreamingResponse:
+ def with_streaming_response(self) -> AsyncProjectsResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
- return AsyncProjectResourceWithStreamingResponse(self)
+ return AsyncProjectsResourceWithStreamingResponse(self)
async def retrieve(
self,
+ id: str,
*,
- project_id: str,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -111,11 +105,9 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProjectRetrieveResponse:
"""
- Get project details for the specified project.
+ Get project details by ID.
Args:
- project_id: The project ID
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -124,52 +116,48 @@ async def retrieve(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._get(
- "/v3/project",
+ f"/v3/projects/{id}",
options=make_request_options(
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- query=await async_maybe_transform(
- {"project_id": project_id}, project_retrieve_params.ProjectRetrieveParams
- ),
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=ProjectRetrieveResponse,
)
-class ProjectResourceWithRawResponse:
- def __init__(self, project: ProjectResource) -> None:
- self._project = project
+class ProjectsResourceWithRawResponse:
+ def __init__(self, projects: ProjectsResource) -> None:
+ self._projects = projects
self.retrieve = to_raw_response_wrapper(
- project.retrieve,
+ projects.retrieve,
)
-class AsyncProjectResourceWithRawResponse:
- def __init__(self, project: AsyncProjectResource) -> None:
- self._project = project
+class AsyncProjectsResourceWithRawResponse:
+ def __init__(self, projects: AsyncProjectsResource) -> None:
+ self._projects = projects
self.retrieve = async_to_raw_response_wrapper(
- project.retrieve,
+ projects.retrieve,
)
-class ProjectResourceWithStreamingResponse:
- def __init__(self, project: ProjectResource) -> None:
- self._project = project
+class ProjectsResourceWithStreamingResponse:
+ def __init__(self, projects: ProjectsResource) -> None:
+ self._projects = projects
self.retrieve = to_streamed_response_wrapper(
- project.retrieve,
+ projects.retrieve,
)
-class AsyncProjectResourceWithStreamingResponse:
- def __init__(self, project: AsyncProjectResource) -> None:
- self._project = project
+class AsyncProjectsResourceWithStreamingResponse:
+ def __init__(self, projects: AsyncProjectsResource) -> None:
+ self._projects = projects
self.retrieve = async_to_streamed_response_wrapper(
- project.retrieve,
+ projects.retrieve,
)
diff --git a/src/unlayer/resources/templates.py b/src/unlayer/resources/templates.py
index 4c0708a..e41c931 100644
--- a/src/unlayer/resources/templates.py
+++ b/src/unlayer/resources/templates.py
@@ -32,7 +32,7 @@ def with_raw_response(self) -> TemplatesResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return TemplatesResourceWithRawResponse(self)
@@ -41,7 +41,7 @@ def with_streaming_response(self) -> TemplatesResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return TemplatesResourceWithStreamingResponse(self)
@@ -49,7 +49,7 @@ def retrieve(
self,
id: str,
*,
- project_id: str,
+ project_id: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -61,7 +61,7 @@ def retrieve(
Get template by ID.
Args:
- project_id: The project ID
+ project_id: The project ID (required for PAT auth, auto-resolved for API key auth)
extra_headers: Send extra headers
@@ -88,11 +88,11 @@ def retrieve(
def list(
self,
*,
- project_id: str,
cursor: str | Omit = omit,
display_mode: Literal["email", "web", "document"] | Omit = omit,
limit: int | Omit = omit,
name: str | Omit = omit,
+ project_id: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -106,8 +106,6 @@ def list(
order by update time.
Args:
- project_id: The project ID to list templates for
-
cursor: Pagination cursor from previous response
display_mode: Filter by template type
@@ -116,6 +114,8 @@ def list(
name: Filter by name (case-insensitive search)
+ project_id: The project ID to list templates for
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -134,11 +134,11 @@ def list(
timeout=timeout,
query=maybe_transform(
{
- "project_id": project_id,
"cursor": cursor,
"display_mode": display_mode,
"limit": limit,
"name": name,
+ "project_id": project_id,
},
template_list_params.TemplateListParams,
),
@@ -154,7 +154,7 @@ def with_raw_response(self) -> AsyncTemplatesResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return AsyncTemplatesResourceWithRawResponse(self)
@@ -163,7 +163,7 @@ def with_streaming_response(self) -> AsyncTemplatesResourceWithStreamingResponse
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return AsyncTemplatesResourceWithStreamingResponse(self)
@@ -171,7 +171,7 @@ async def retrieve(
self,
id: str,
*,
- project_id: str,
+ project_id: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -183,7 +183,7 @@ async def retrieve(
Get template by ID.
Args:
- project_id: The project ID
+ project_id: The project ID (required for PAT auth, auto-resolved for API key auth)
extra_headers: Send extra headers
@@ -212,11 +212,11 @@ async def retrieve(
def list(
self,
*,
- project_id: str,
cursor: str | Omit = omit,
display_mode: Literal["email", "web", "document"] | Omit = omit,
limit: int | Omit = omit,
name: str | Omit = omit,
+ project_id: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -230,8 +230,6 @@ def list(
order by update time.
Args:
- project_id: The project ID to list templates for
-
cursor: Pagination cursor from previous response
display_mode: Filter by template type
@@ -240,6 +238,8 @@ def list(
name: Filter by name (case-insensitive search)
+ project_id: The project ID to list templates for
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -258,11 +258,11 @@ def list(
timeout=timeout,
query=maybe_transform(
{
- "project_id": project_id,
"cursor": cursor,
"display_mode": display_mode,
"limit": limit,
"name": name,
+ "project_id": project_id,
},
template_list_params.TemplateListParams,
),
diff --git a/src/unlayer/resources/workspaces.py b/src/unlayer/resources/workspaces.py
index 494ccea..3bce476 100644
--- a/src/unlayer/resources/workspaces.py
+++ b/src/unlayer/resources/workspaces.py
@@ -27,7 +27,7 @@ def with_raw_response(self) -> WorkspacesResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return WorkspacesResourceWithRawResponse(self)
@@ -36,7 +36,7 @@ def with_streaming_response(self) -> WorkspacesResourceWithStreamingResponse:
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return WorkspacesResourceWithStreamingResponse(self)
@@ -51,8 +51,10 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspaceRetrieveResponse:
- """
- Get a specific workspace by ID with its projects.
+ """Get a specific workspace by ID with its projects.
+
+ Requires a Personal Access
+ Token (PAT).
Args:
extra_headers: Send extra headers
@@ -83,7 +85,11 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspaceListResponse:
- """Get all workspaces accessible by the current token."""
+ """Get all workspaces accessible by the current token.
+
+ Requires a Personal Access
+ Token (PAT).
+ """
return self._get(
"/v3/workspaces",
options=make_request_options(
@@ -100,7 +106,7 @@ def with_raw_response(self) -> AsyncWorkspacesResourceWithRawResponse:
This property can be used as a prefix for any HTTP method call to return
the raw response object instead of the parsed content.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#accessing-raw-response-data-eg-headers
+ For more information, see https://www.github.com/unlayer/unlayer-python#accessing-raw-response-data-eg-headers
"""
return AsyncWorkspacesResourceWithRawResponse(self)
@@ -109,7 +115,7 @@ def with_streaming_response(self) -> AsyncWorkspacesResourceWithStreamingRespons
"""
An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- For more information, see https://www.github.com/stainless-sdks/unlayer-python#with_streaming_response
+ For more information, see https://www.github.com/unlayer/unlayer-python#with_streaming_response
"""
return AsyncWorkspacesResourceWithStreamingResponse(self)
@@ -124,8 +130,10 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspaceRetrieveResponse:
- """
- Get a specific workspace by ID with its projects.
+ """Get a specific workspace by ID with its projects.
+
+ Requires a Personal Access
+ Token (PAT).
Args:
extra_headers: Send extra headers
@@ -156,7 +164,11 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspaceListResponse:
- """Get all workspaces accessible by the current token."""
+ """Get all workspaces accessible by the current token.
+
+ Requires a Personal Access
+ Token (PAT).
+ """
return await self._get(
"/v3/workspaces",
options=make_request_options(
diff --git a/src/unlayer/types/__init__.py b/src/unlayer/types/__init__.py
index 33221d8..3aa8608 100644
--- a/src/unlayer/types/__init__.py
+++ b/src/unlayer/types/__init__.py
@@ -4,7 +4,6 @@
from .template_list_params import TemplateListParams as TemplateListParams
from .template_list_response import TemplateListResponse as TemplateListResponse
-from .project_retrieve_params import ProjectRetrieveParams as ProjectRetrieveParams
from .workspace_list_response import WorkspaceListResponse as WorkspaceListResponse
from .template_retrieve_params import TemplateRetrieveParams as TemplateRetrieveParams
from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse
diff --git a/src/unlayer/types/project_retrieve_params.py b/src/unlayer/types/project_retrieve_params.py
deleted file mode 100644
index d2299de..0000000
--- a/src/unlayer/types/project_retrieve_params.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-
-from __future__ import annotations
-
-from typing_extensions import Required, Annotated, TypedDict
-
-from .._utils import PropertyInfo
-
-__all__ = ["ProjectRetrieveParams"]
-
-
-class ProjectRetrieveParams(TypedDict, total=False):
- project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]]
- """The project ID"""
diff --git a/src/unlayer/types/template_list_params.py b/src/unlayer/types/template_list_params.py
index afb5c25..79a4a48 100644
--- a/src/unlayer/types/template_list_params.py
+++ b/src/unlayer/types/template_list_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing_extensions import Literal, Annotated, TypedDict
from .._utils import PropertyInfo
@@ -10,9 +10,6 @@
class TemplateListParams(TypedDict, total=False):
- project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]]
- """The project ID to list templates for"""
-
cursor: str
"""Pagination cursor from previous response"""
@@ -24,3 +21,6 @@ class TemplateListParams(TypedDict, total=False):
name: str
"""Filter by name (case-insensitive search)"""
+
+ project_id: Annotated[str, PropertyInfo(alias="projectId")]
+ """The project ID to list templates for"""
diff --git a/src/unlayer/types/template_retrieve_params.py b/src/unlayer/types/template_retrieve_params.py
index c7ca6cf..d5af5fa 100644
--- a/src/unlayer/types/template_retrieve_params.py
+++ b/src/unlayer/types/template_retrieve_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import Required, Annotated, TypedDict
+from typing_extensions import Annotated, TypedDict
from .._utils import PropertyInfo
@@ -10,5 +10,5 @@
class TemplateRetrieveParams(TypedDict, total=False):
- project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]]
- """The project ID"""
+ project_id: Annotated[str, PropertyInfo(alias="projectId")]
+ """The project ID (required for PAT auth, auto-resolved for API key auth)"""
diff --git a/tests/api_resources/test_project.py b/tests/api_resources/test_projects.py
similarity index 69%
rename from tests/api_resources/test_project.py
rename to tests/api_resources/test_projects.py
index 5719de6..3b4276d 100644
--- a/tests/api_resources/test_project.py
+++ b/tests/api_resources/test_projects.py
@@ -14,20 +14,20 @@
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-class TestProject:
+class TestProjects:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
@parametrize
def test_method_retrieve(self, client: Unlayer) -> None:
- project = client.project.retrieve(
- project_id="projectId",
+ project = client.projects.retrieve(
+ "id",
)
assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
@parametrize
def test_raw_response_retrieve(self, client: Unlayer) -> None:
- response = client.project.with_raw_response.retrieve(
- project_id="projectId",
+ response = client.projects.with_raw_response.retrieve(
+ "id",
)
assert response.is_closed is True
@@ -37,8 +37,8 @@ def test_raw_response_retrieve(self, client: Unlayer) -> None:
@parametrize
def test_streaming_response_retrieve(self, client: Unlayer) -> None:
- with client.project.with_streaming_response.retrieve(
- project_id="projectId",
+ with client.projects.with_streaming_response.retrieve(
+ "id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -48,23 +48,30 @@ def test_streaming_response_retrieve(self, client: Unlayer) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_path_params_retrieve(self, client: Unlayer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.projects.with_raw_response.retrieve(
+ "",
+ )
+
-class TestAsyncProject:
+class TestAsyncProjects:
parametrize = pytest.mark.parametrize(
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
@parametrize
async def test_method_retrieve(self, async_client: AsyncUnlayer) -> None:
- project = await async_client.project.retrieve(
- project_id="projectId",
+ project = await async_client.projects.retrieve(
+ "id",
)
assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
@parametrize
async def test_raw_response_retrieve(self, async_client: AsyncUnlayer) -> None:
- response = await async_client.project.with_raw_response.retrieve(
- project_id="projectId",
+ response = await async_client.projects.with_raw_response.retrieve(
+ "id",
)
assert response.is_closed is True
@@ -74,8 +81,8 @@ async def test_raw_response_retrieve(self, async_client: AsyncUnlayer) -> None:
@parametrize
async def test_streaming_response_retrieve(self, async_client: AsyncUnlayer) -> None:
- async with async_client.project.with_streaming_response.retrieve(
- project_id="projectId",
+ async with async_client.projects.with_streaming_response.retrieve(
+ "id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -84,3 +91,10 @@ async def test_streaming_response_retrieve(self, async_client: AsyncUnlayer) ->
assert_matches_type(ProjectRetrieveResponse, project, path=["response"])
assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncUnlayer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.projects.with_raw_response.retrieve(
+ "",
+ )
diff --git a/tests/api_resources/test_templates.py b/tests/api_resources/test_templates.py
index 4a2291b..bea7f32 100644
--- a/tests/api_resources/test_templates.py
+++ b/tests/api_resources/test_templates.py
@@ -20,6 +20,13 @@ class TestTemplates:
@parametrize
def test_method_retrieve(self, client: Unlayer) -> None:
+ template = client.templates.retrieve(
+ id="id",
+ )
+ assert_matches_type(TemplateRetrieveResponse, template, path=["response"])
+
+ @parametrize
+ def test_method_retrieve_with_all_params(self, client: Unlayer) -> None:
template = client.templates.retrieve(
id="id",
project_id="projectId",
@@ -30,7 +37,6 @@ def test_method_retrieve(self, client: Unlayer) -> None:
def test_raw_response_retrieve(self, client: Unlayer) -> None:
response = client.templates.with_raw_response.retrieve(
id="id",
- project_id="projectId",
)
assert response.is_closed is True
@@ -42,7 +48,6 @@ def test_raw_response_retrieve(self, client: Unlayer) -> None:
def test_streaming_response_retrieve(self, client: Unlayer) -> None:
with client.templates.with_streaming_response.retrieve(
id="id",
- project_id="projectId",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -57,32 +62,27 @@ def test_path_params_retrieve(self, client: Unlayer) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
client.templates.with_raw_response.retrieve(
id="",
- project_id="projectId",
)
@parametrize
def test_method_list(self, client: Unlayer) -> None:
- template = client.templates.list(
- project_id="projectId",
- )
+ template = client.templates.list()
assert_matches_type(SyncCursorPage[TemplateListResponse], template, path=["response"])
@parametrize
def test_method_list_with_all_params(self, client: Unlayer) -> None:
template = client.templates.list(
- project_id="projectId",
cursor="cursor",
display_mode="email",
limit=1,
name="name",
+ project_id="projectId",
)
assert_matches_type(SyncCursorPage[TemplateListResponse], template, path=["response"])
@parametrize
def test_raw_response_list(self, client: Unlayer) -> None:
- response = client.templates.with_raw_response.list(
- project_id="projectId",
- )
+ response = client.templates.with_raw_response.list()
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -91,9 +91,7 @@ def test_raw_response_list(self, client: Unlayer) -> None:
@parametrize
def test_streaming_response_list(self, client: Unlayer) -> None:
- with client.templates.with_streaming_response.list(
- project_id="projectId",
- ) as response:
+ with client.templates.with_streaming_response.list() as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -110,6 +108,13 @@ class TestAsyncTemplates:
@parametrize
async def test_method_retrieve(self, async_client: AsyncUnlayer) -> None:
+ template = await async_client.templates.retrieve(
+ id="id",
+ )
+ assert_matches_type(TemplateRetrieveResponse, template, path=["response"])
+
+ @parametrize
+ async def test_method_retrieve_with_all_params(self, async_client: AsyncUnlayer) -> None:
template = await async_client.templates.retrieve(
id="id",
project_id="projectId",
@@ -120,7 +125,6 @@ async def test_method_retrieve(self, async_client: AsyncUnlayer) -> None:
async def test_raw_response_retrieve(self, async_client: AsyncUnlayer) -> None:
response = await async_client.templates.with_raw_response.retrieve(
id="id",
- project_id="projectId",
)
assert response.is_closed is True
@@ -132,7 +136,6 @@ async def test_raw_response_retrieve(self, async_client: AsyncUnlayer) -> None:
async def test_streaming_response_retrieve(self, async_client: AsyncUnlayer) -> None:
async with async_client.templates.with_streaming_response.retrieve(
id="id",
- project_id="projectId",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -147,32 +150,27 @@ async def test_path_params_retrieve(self, async_client: AsyncUnlayer) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
await async_client.templates.with_raw_response.retrieve(
id="",
- project_id="projectId",
)
@parametrize
async def test_method_list(self, async_client: AsyncUnlayer) -> None:
- template = await async_client.templates.list(
- project_id="projectId",
- )
+ template = await async_client.templates.list()
assert_matches_type(AsyncCursorPage[TemplateListResponse], template, path=["response"])
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncUnlayer) -> None:
template = await async_client.templates.list(
- project_id="projectId",
cursor="cursor",
display_mode="email",
limit=1,
name="name",
+ project_id="projectId",
)
assert_matches_type(AsyncCursorPage[TemplateListResponse], template, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncUnlayer) -> None:
- response = await async_client.templates.with_raw_response.list(
- project_id="projectId",
- )
+ response = await async_client.templates.with_raw_response.list()
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -181,9 +179,7 @@ async def test_raw_response_list(self, async_client: AsyncUnlayer) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncUnlayer) -> None:
- async with async_client.templates.with_streaming_response.list(
- project_id="projectId",
- ) as response:
+ async with async_client.templates.with_streaming_response.list() as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/conftest.py b/tests/conftest.py
index 0fed985..fc6ba41 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -45,7 +45,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-access_token = "My Access Token"
+api_key = "My API Key"
@pytest.fixture(scope="session")
@@ -54,7 +54,7 @@ def client(request: FixtureRequest) -> Iterator[Unlayer]:
if not isinstance(strict, bool):
raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
- with Unlayer(base_url=base_url, access_token=access_token, _strict_response_validation=strict) as client:
+ with Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client:
yield client
@@ -79,6 +79,6 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncUnlayer]:
raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
async with AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=strict, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client
) as client:
yield client
diff --git a/tests/test_client.py b/tests/test_client.py
index 97ed357..071a7bc 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -23,7 +23,7 @@
from unlayer._types import Omit
from unlayer._utils import asyncify
from unlayer._models import BaseModel, FinalRequestOptions
-from unlayer._exceptions import UnlayerError, APIStatusError, APITimeoutError, APIResponseValidationError
+from unlayer._exceptions import APIStatusError, APIResponseValidationError
from unlayer._base_client import (
DEFAULT_TIMEOUT,
HTTPX_DEFAULT_TIMEOUT,
@@ -39,7 +39,7 @@
T = TypeVar("T")
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
-access_token = "My Access Token"
+api_key = "My API Key"
def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
@@ -103,14 +103,6 @@ async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter]
yield item
-def _get_open_connections(client: Unlayer | AsyncUnlayer) -> int:
- transport = client._client._transport
- assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
-
- pool = transport._pool
- return len(pool._requests)
-
-
class TestUnlayer:
@pytest.mark.respx(base_url=base_url)
def test_raw_response(self, respx_mock: MockRouter, client: Unlayer) -> None:
@@ -136,9 +128,9 @@ def test_copy(self, client: Unlayer) -> None:
copied = client.copy()
assert id(copied) != id(client)
- copied = client.copy(access_token="another My Access Token")
- assert copied.access_token == "another My Access Token"
- assert client.access_token == "My Access Token"
+ copied = client.copy(api_key="another My API Key")
+ assert copied.api_key == "another My API Key"
+ assert client.api_key == "My API Key"
def test_copy_default_options(self, client: Unlayer) -> None:
# options that have a default are overridden correctly
@@ -158,10 +150,7 @@ def test_copy_default_options(self, client: Unlayer) -> None:
def test_copy_default_headers(self) -> None:
client = Unlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
assert client.default_headers["X-Foo"] == "bar"
@@ -196,7 +185,7 @@ def test_copy_default_headers(self) -> None:
def test_copy_default_query(self) -> None:
client = Unlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, default_query={"foo": "bar"}
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"}
)
assert _get_params(client)["foo"] == "bar"
@@ -321,9 +310,7 @@ def test_request_timeout(self, client: Unlayer) -> None:
assert timeout == httpx.Timeout(100.0)
def test_client_timeout_option(self) -> None:
- client = Unlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, timeout=httpx.Timeout(0)
- )
+ client = Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0))
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
@@ -335,7 +322,7 @@ def test_http_client_timeout_option(self) -> None:
# custom timeout given to the httpx client should be used
with httpx.Client(timeout=None) as http_client:
client = Unlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -347,7 +334,7 @@ def test_http_client_timeout_option(self) -> None:
# no timeout given to the httpx client should not use the httpx default
with httpx.Client() as http_client:
client = Unlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -359,7 +346,7 @@ def test_http_client_timeout_option(self) -> None:
# explicitly passing the default timeout currently results in it being ignored
with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
client = Unlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -373,17 +360,14 @@ async def test_invalid_http_client(self) -> None:
async with httpx.AsyncClient() as http_client:
Unlayer(
base_url=base_url,
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=cast(Any, http_client),
)
def test_default_headers_option(self) -> None:
test_client = Unlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
@@ -391,7 +375,7 @@ def test_default_headers_option(self) -> None:
test_client2 = Unlayer(
base_url=base_url,
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
default_headers={
"X-Foo": "stainless",
@@ -406,21 +390,27 @@ def test_default_headers_option(self) -> None:
test_client2.close()
def test_validate_headers(self) -> None:
- client = Unlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ client = Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
- assert request.headers.get("Authorization") == f"Bearer {access_token}"
+ assert request.headers.get("Authorization") == f"Bearer {api_key}"
- with pytest.raises(UnlayerError):
- with update_env(**{"UNLAYER_ACCESS_TOKEN": Omit()}):
- client2 = Unlayer(base_url=base_url, access_token=None, _strict_response_validation=True)
- _ = client2
+ with update_env(**{"UNLAYER_API_KEY": Omit()}):
+ client2 = Unlayer(base_url=base_url, api_key=None, _strict_response_validation=True)
+
+ with pytest.raises(
+ TypeError,
+ match="Could not resolve authentication method. Expected either api_key or personal_access_token to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted",
+ ):
+ client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+
+ request2 = client2._build_request(
+ FinalRequestOptions(method="get", url="/foo", headers={"Authorization": Omit()})
+ )
+ assert request2.headers.get("Authorization") is None
def test_default_query_option(self) -> None:
client = Unlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- default_query={"query_param": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
url = httpx.URL(request.url)
@@ -592,7 +582,7 @@ def mock_handler(request: httpx.Request) -> httpx.Response:
with Unlayer(
base_url=base_url,
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
) as client:
@@ -686,9 +676,7 @@ class Model(BaseModel):
assert response.foo == 2
def test_base_url_setter(self) -> None:
- client = Unlayer(
- base_url="https://example.com/from_init", access_token=access_token, _strict_response_validation=True
- )
+ client = Unlayer(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True)
assert client.base_url == "https://example.com/from_init/"
client.base_url = "https://example.com/from_setter" # type: ignore[assignment]
@@ -699,32 +687,16 @@ def test_base_url_setter(self) -> None:
def test_base_url_env(self) -> None:
with update_env(UNLAYER_BASE_URL="http://localhost:5000/from/env"):
- client = Unlayer(access_token=access_token, _strict_response_validation=True)
+ client = Unlayer(api_key=api_key, _strict_response_validation=True)
assert client.base_url == "http://localhost:5000/from/env/"
- # explicit environment arg requires explicitness
- with update_env(UNLAYER_BASE_URL="http://localhost:5000/from/env"):
- with pytest.raises(ValueError, match=r"you must pass base_url=None"):
- Unlayer(access_token=access_token, _strict_response_validation=True, environment="production")
-
- client = Unlayer(
- base_url=None, access_token=access_token, _strict_response_validation=True, environment="production"
- )
- assert str(client.base_url).startswith("https://api.unlayer.com")
-
- client.close()
-
@pytest.mark.parametrize(
"client",
[
+ Unlayer(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True),
Unlayer(
base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
- _strict_response_validation=True,
- ),
- Unlayer(
- base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.Client(),
),
@@ -745,14 +717,10 @@ def test_base_url_trailing_slash(self, client: Unlayer) -> None:
@pytest.mark.parametrize(
"client",
[
+ Unlayer(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True),
Unlayer(
base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
- _strict_response_validation=True,
- ),
- Unlayer(
- base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.Client(),
),
@@ -773,14 +741,10 @@ def test_base_url_no_trailing_slash(self, client: Unlayer) -> None:
@pytest.mark.parametrize(
"client",
[
+ Unlayer(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True),
Unlayer(
base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
- _strict_response_validation=True,
- ),
- Unlayer(
- base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.Client(),
),
@@ -799,7 +763,7 @@ def test_absolute_request_url(self, client: Unlayer) -> None:
client.close()
def test_copied_client_does_not_close_http(self) -> None:
- test_client = Unlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ test_client = Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
assert not test_client.is_closed()
copied = test_client.copy()
@@ -810,7 +774,7 @@ def test_copied_client_does_not_close_http(self) -> None:
assert not test_client.is_closed()
def test_client_context_manager(self) -> None:
- test_client = Unlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ test_client = Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
with test_client as c2:
assert c2 is test_client
assert not c2.is_closed()
@@ -831,12 +795,7 @@ class Model(BaseModel):
def test_client_max_retries_validation(self) -> None:
with pytest.raises(TypeError, match=r"max_retries cannot be None"):
- Unlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- max_retries=cast(Any, None),
- )
+ Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None))
@pytest.mark.respx(base_url=base_url)
def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
@@ -845,12 +804,12 @@ class Model(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
- strict_client = Unlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ strict_client = Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
with pytest.raises(APIResponseValidationError):
strict_client.get("/foo", cast_to=Model)
- non_strict_client = Unlayer(base_url=base_url, access_token=access_token, _strict_response_validation=False)
+ non_strict_client = Unlayer(base_url=base_url, api_key=api_key, _strict_response_validation=False)
response = non_strict_client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]
@@ -888,25 +847,6 @@ def test_parse_retry_after_header(
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]
- @mock.patch("unlayer._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
- @pytest.mark.respx(base_url=base_url)
- def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Unlayer) -> None:
- respx_mock.get("/v3/project").mock(side_effect=httpx.TimeoutException("Test timeout error"))
-
- with pytest.raises(APITimeoutError):
- client.project.with_streaming_response.retrieve(project_id="projectId").__enter__()
-
- assert _get_open_connections(client) == 0
-
- @mock.patch("unlayer._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
- @pytest.mark.respx(base_url=base_url)
- def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Unlayer) -> None:
- respx_mock.get("/v3/project").mock(return_value=httpx.Response(500))
-
- with pytest.raises(APIStatusError):
- client.project.with_streaming_response.retrieve(project_id="projectId").__enter__()
- assert _get_open_connections(client) == 0
-
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("unlayer._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
@@ -931,9 +871,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)
- respx_mock.get("/v3/project").mock(side_effect=retry_handler)
+ respx_mock.get("/v3/templates").mock(side_effect=retry_handler)
- response = client.project.with_raw_response.retrieve(project_id="projectId")
+ response = client.templates.with_raw_response.list()
assert response.retries_taken == failures_before_success
assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
@@ -955,11 +895,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)
- respx_mock.get("/v3/project").mock(side_effect=retry_handler)
+ respx_mock.get("/v3/templates").mock(side_effect=retry_handler)
- response = client.project.with_raw_response.retrieve(
- project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()}
- )
+ response = client.templates.with_raw_response.list(extra_headers={"x-stainless-retry-count": Omit()})
assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
@@ -980,17 +918,17 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)
- respx_mock.get("/v3/project").mock(side_effect=retry_handler)
+ respx_mock.get("/v3/templates").mock(side_effect=retry_handler)
- response = client.project.with_raw_response.retrieve(
- project_id="projectId", extra_headers={"x-stainless-retry-count": "42"}
- )
+ response = client.templates.with_raw_response.list(extra_headers={"x-stainless-retry-count": "42"})
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+ # Delete in case our environment has this set
+ monkeypatch.delenv("HTTP_PROXY", raising=False)
client = DefaultHttpxClient()
@@ -1061,9 +999,9 @@ def test_copy(self, async_client: AsyncUnlayer) -> None:
copied = async_client.copy()
assert id(copied) != id(async_client)
- copied = async_client.copy(access_token="another My Access Token")
- assert copied.access_token == "another My Access Token"
- assert async_client.access_token == "My Access Token"
+ copied = async_client.copy(api_key="another My API Key")
+ assert copied.api_key == "another My API Key"
+ assert async_client.api_key == "My API Key"
def test_copy_default_options(self, async_client: AsyncUnlayer) -> None:
# options that have a default are overridden correctly
@@ -1083,10 +1021,7 @@ def test_copy_default_options(self, async_client: AsyncUnlayer) -> None:
async def test_copy_default_headers(self) -> None:
client = AsyncUnlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
assert client.default_headers["X-Foo"] == "bar"
@@ -1121,7 +1056,7 @@ async def test_copy_default_headers(self) -> None:
async def test_copy_default_query(self) -> None:
client = AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, default_query={"foo": "bar"}
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"}
)
assert _get_params(client)["foo"] == "bar"
@@ -1249,7 +1184,7 @@ async def test_request_timeout(self, async_client: AsyncUnlayer) -> None:
async def test_client_timeout_option(self) -> None:
client = AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, timeout=httpx.Timeout(0)
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1262,7 +1197,7 @@ async def test_http_client_timeout_option(self) -> None:
# custom timeout given to the httpx client should be used
async with httpx.AsyncClient(timeout=None) as http_client:
client = AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1274,7 +1209,7 @@ async def test_http_client_timeout_option(self) -> None:
# no timeout given to the httpx client should not use the httpx default
async with httpx.AsyncClient() as http_client:
client = AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1286,7 +1221,7 @@ async def test_http_client_timeout_option(self) -> None:
# explicitly passing the default timeout currently results in it being ignored
async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
client = AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1300,17 +1235,14 @@ def test_invalid_http_client(self) -> None:
with httpx.Client() as http_client:
AsyncUnlayer(
base_url=base_url,
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=cast(Any, http_client),
)
async def test_default_headers_option(self) -> None:
test_client = AsyncUnlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- default_headers={"X-Foo": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
@@ -1318,7 +1250,7 @@ async def test_default_headers_option(self) -> None:
test_client2 = AsyncUnlayer(
base_url=base_url,
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
default_headers={
"X-Foo": "stainless",
@@ -1333,21 +1265,27 @@ async def test_default_headers_option(self) -> None:
await test_client2.close()
def test_validate_headers(self) -> None:
- client = AsyncUnlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ client = AsyncUnlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
- assert request.headers.get("Authorization") == f"Bearer {access_token}"
+ assert request.headers.get("Authorization") == f"Bearer {api_key}"
- with pytest.raises(UnlayerError):
- with update_env(**{"UNLAYER_ACCESS_TOKEN": Omit()}):
- client2 = AsyncUnlayer(base_url=base_url, access_token=None, _strict_response_validation=True)
- _ = client2
+ with update_env(**{"UNLAYER_API_KEY": Omit()}):
+ client2 = AsyncUnlayer(base_url=base_url, api_key=None, _strict_response_validation=True)
+
+ with pytest.raises(
+ TypeError,
+ match="Could not resolve authentication method. Expected either api_key or personal_access_token to be set. Or for one of the `Authorization` or `Authorization` headers to be explicitly omitted",
+ ):
+ client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+
+ request2 = client2._build_request(
+ FinalRequestOptions(method="get", url="/foo", headers={"Authorization": Omit()})
+ )
+ assert request2.headers.get("Authorization") is None
async def test_default_query_option(self) -> None:
client = AsyncUnlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- default_query={"query_param": "bar"},
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
url = httpx.URL(request.url)
@@ -1519,7 +1457,7 @@ async def mock_handler(request: httpx.Request) -> httpx.Response:
async with AsyncUnlayer(
base_url=base_url,
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
) as client:
@@ -1618,7 +1556,7 @@ class Model(BaseModel):
async def test_base_url_setter(self) -> None:
client = AsyncUnlayer(
- base_url="https://example.com/from_init", access_token=access_token, _strict_response_validation=True
+ base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True
)
assert client.base_url == "https://example.com/from_init/"
@@ -1630,32 +1568,18 @@ async def test_base_url_setter(self) -> None:
async def test_base_url_env(self) -> None:
with update_env(UNLAYER_BASE_URL="http://localhost:5000/from/env"):
- client = AsyncUnlayer(access_token=access_token, _strict_response_validation=True)
+ client = AsyncUnlayer(api_key=api_key, _strict_response_validation=True)
assert client.base_url == "http://localhost:5000/from/env/"
- # explicit environment arg requires explicitness
- with update_env(UNLAYER_BASE_URL="http://localhost:5000/from/env"):
- with pytest.raises(ValueError, match=r"you must pass base_url=None"):
- AsyncUnlayer(access_token=access_token, _strict_response_validation=True, environment="production")
-
- client = AsyncUnlayer(
- base_url=None, access_token=access_token, _strict_response_validation=True, environment="production"
- )
- assert str(client.base_url).startswith("https://api.unlayer.com")
-
- await client.close()
-
@pytest.mark.parametrize(
"client",
[
AsyncUnlayer(
- base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
AsyncUnlayer(
base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.AsyncClient(),
),
@@ -1677,13 +1601,11 @@ async def test_base_url_trailing_slash(self, client: AsyncUnlayer) -> None:
"client",
[
AsyncUnlayer(
- base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
AsyncUnlayer(
base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.AsyncClient(),
),
@@ -1705,13 +1627,11 @@ async def test_base_url_no_trailing_slash(self, client: AsyncUnlayer) -> None:
"client",
[
AsyncUnlayer(
- base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
- _strict_response_validation=True,
+ base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True
),
AsyncUnlayer(
base_url="http://localhost:5000/custom/path/",
- access_token=access_token,
+ api_key=api_key,
_strict_response_validation=True,
http_client=httpx.AsyncClient(),
),
@@ -1730,7 +1650,7 @@ async def test_absolute_request_url(self, client: AsyncUnlayer) -> None:
await client.close()
async def test_copied_client_does_not_close_http(self) -> None:
- test_client = AsyncUnlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ test_client = AsyncUnlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
assert not test_client.is_closed()
copied = test_client.copy()
@@ -1742,7 +1662,7 @@ async def test_copied_client_does_not_close_http(self) -> None:
assert not test_client.is_closed()
async def test_client_context_manager(self) -> None:
- test_client = AsyncUnlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ test_client = AsyncUnlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
async with test_client as c2:
assert c2 is test_client
assert not c2.is_closed()
@@ -1764,10 +1684,7 @@ class Model(BaseModel):
async def test_client_max_retries_validation(self) -> None:
with pytest.raises(TypeError, match=r"max_retries cannot be None"):
AsyncUnlayer(
- base_url=base_url,
- access_token=access_token,
- _strict_response_validation=True,
- max_retries=cast(Any, None),
+ base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)
)
@pytest.mark.respx(base_url=base_url)
@@ -1777,14 +1694,12 @@ class Model(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
- strict_client = AsyncUnlayer(base_url=base_url, access_token=access_token, _strict_response_validation=True)
+ strict_client = AsyncUnlayer(base_url=base_url, api_key=api_key, _strict_response_validation=True)
with pytest.raises(APIResponseValidationError):
await strict_client.get("/foo", cast_to=Model)
- non_strict_client = AsyncUnlayer(
- base_url=base_url, access_token=access_token, _strict_response_validation=False
- )
+ non_strict_client = AsyncUnlayer(base_url=base_url, api_key=api_key, _strict_response_validation=False)
response = await non_strict_client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]
@@ -1822,27 +1737,6 @@ async def test_parse_retry_after_header(
calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]
- @mock.patch("unlayer._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
- @pytest.mark.respx(base_url=base_url)
- async def test_retrying_timeout_errors_doesnt_leak(
- self, respx_mock: MockRouter, async_client: AsyncUnlayer
- ) -> None:
- respx_mock.get("/v3/project").mock(side_effect=httpx.TimeoutException("Test timeout error"))
-
- with pytest.raises(APITimeoutError):
- await async_client.project.with_streaming_response.retrieve(project_id="projectId").__aenter__()
-
- assert _get_open_connections(async_client) == 0
-
- @mock.patch("unlayer._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
- @pytest.mark.respx(base_url=base_url)
- async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncUnlayer) -> None:
- respx_mock.get("/v3/project").mock(return_value=httpx.Response(500))
-
- with pytest.raises(APIStatusError):
- await async_client.project.with_streaming_response.retrieve(project_id="projectId").__aenter__()
- assert _get_open_connections(async_client) == 0
-
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("unlayer._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
@@ -1867,9 +1761,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)
- respx_mock.get("/v3/project").mock(side_effect=retry_handler)
+ respx_mock.get("/v3/templates").mock(side_effect=retry_handler)
- response = await client.project.with_raw_response.retrieve(project_id="projectId")
+ response = await client.templates.with_raw_response.list()
assert response.retries_taken == failures_before_success
assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
@@ -1891,11 +1785,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)
- respx_mock.get("/v3/project").mock(side_effect=retry_handler)
+ respx_mock.get("/v3/templates").mock(side_effect=retry_handler)
- response = await client.project.with_raw_response.retrieve(
- project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()}
- )
+ response = await client.templates.with_raw_response.list(extra_headers={"x-stainless-retry-count": Omit()})
assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
@@ -1916,11 +1808,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
return httpx.Response(500)
return httpx.Response(200)
- respx_mock.get("/v3/project").mock(side_effect=retry_handler)
+ respx_mock.get("/v3/templates").mock(side_effect=retry_handler)
- response = await client.project.with_raw_response.retrieve(
- project_id="projectId", extra_headers={"x-stainless-retry-count": "42"}
- )
+ response = await client.templates.with_raw_response.list(extra_headers={"x-stainless-retry-count": "42"})
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
@@ -1931,6 +1821,8 @@ async def test_get_platform(self) -> None:
async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly
monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+ # Delete in case our environment has this set
+ monkeypatch.delenv("HTTP_PROXY", raising=False)
client = DefaultAsyncHttpxClient()