photonhq-api 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ node_modules/
2
+ dist/
3
+ .test-dist/
4
+ .venv/
5
+ __pycache__/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .mypy_cache/
9
+ target/
10
+ artifacts/
11
+ *.egg-info/
12
+ *.pyc
13
+ .DS_Store
14
+ .env
15
+ .env.*
16
+ !.env.example
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Photon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.4
2
+ Name: photonhq-api
3
+ Version: 0.1.0
4
+ Summary: Generated Photon API RPC client
5
+ Project-URL: Repository, https://github.com/photon-hq/api
6
+ Author: Photon
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: email-validator<3,>=2.3.0
11
+ Requires-Dist: httpx<1,>=0.28.1
12
+ Requires-Dist: pydantic<2.14,>=2.13.5
13
+ Description-Content-Type: text/markdown
14
+
15
+ # photonhq-api
16
+
17
+ Synchronous and asynchronous Python client for the Photon API, generated from the
18
+ public OpenAPI contract. Requires Python 3.11+ and imports as `photon_api`.
19
+
20
+ > **Preview release.** This version is generated from the current production
21
+ > contract before all of its schemas have stable names. Types the contract
22
+ > does not name yet have names derived from their operation (for example
23
+ > `ListProjectsResponse200ApplicationJson`) or carry a version prefix such as
24
+ > `Photon20260701_`. These type names change in 0.2.0, when the contract
25
+ > names them; the rename does not change requests, responses or method names.
26
+
27
+ ## Installation
28
+
29
+ ```sh
30
+ pip install photonhq-api
31
+ ```
32
+
33
+ ## Quickstart
34
+
35
+ ```python
36
+ import os
37
+
38
+ from photon_api import Photon
39
+ from photon_api.rpc_generated import CountProjectsInput
40
+
41
+ token = os.environ["PHOTON_API_TOKEN"]
42
+ request = CountProjectsInput.model_validate(
43
+ {"path": {"organizationId": os.environ["PHOTON_ORGANIZATION_ID"]}}
44
+ )
45
+
46
+ with Photon(headers={"Authorization": f"Bearer {token}"}) as photon:
47
+ result = photon.organizations.projects.count(request)
48
+ print(result.count)
49
+ ```
50
+
51
+ `countProjects` accepts an Account Service Key or an Organization Service Identity
52
+ API key / M2M access token. Set `PHOTON_API_TOKEN` to one of those credentials
53
+ and `PHOTON_ORGANIZATION_ID` to the ID of an organization it can access. See the
54
+ Photon dashboard and documentation to create credentials and find IDs.
55
+
56
+ ## Authentication
57
+
58
+ Every credential is sent as `Authorization: Bearer <credential>`. Pass it through
59
+ the `headers` option. The client does not choose or check a credential type; the
60
+ API accepts or rejects it.
61
+
62
+ | Credential | Security scheme | Notes |
63
+ | --- | --- | --- |
64
+ | Account Service Key (`pho_ask_...`) | `accountServiceKey` | Acts as the account and reaches every route the account can. |
65
+ | Project API key (`pho_sk_...`) | `projectApiKey` | Bound to one project. Accepted only under `/v1/projects/{projectId}`. |
66
+ | Organization Service Identity API key or M2M access token | `serviceIdentityBearer` | Restricted to its organization and explicitly granted permissions. Never send an M2M client secret to an API endpoint. |
67
+ | OAuth access token | `oauth2` | Scope is intersected with the permissions the route grants; an empty intersection returns `403 insufficient_scope`. |
68
+
69
+ Each operation's `security` entry in the OpenAPI contract lists the credential
70
+ types it accepts. A few operations accept requests without credentials.
71
+
72
+ The SDK does not run OAuth flows. Obtain OAuth access tokens and manage refresh
73
+ in your application's own authentication flow, then supply the current token.
74
+
75
+ `headers` accepts a mapping or a callable returning one. The callable runs before
76
+ every attempt, including retries, so it can return a freshly refreshed token.
77
+ For `AsyncPhoton` the callable may be async; for `Photon` it must be synchronous.
78
+
79
+ ## Using the client
80
+
81
+ ```python
82
+ import asyncio
83
+
84
+ from photon_api import AsyncPhoton
85
+
86
+
87
+ async def main() -> None:
88
+ async with AsyncPhoton(headers={"Authorization": f"Bearer {token}"}) as photon:
89
+ result = await photon.organizations.projects.count(request)
90
+ print(result.count)
91
+
92
+
93
+ asyncio.run(main())
94
+ ```
95
+
96
+ - `Photon` is synchronous; `AsyncPhoton` has the same methods as coroutines.
97
+ - Operations are grouped into namespaces that follow the API paths, for example
98
+ `photon.organizations.projects.count`. Namespaces and methods are snake_case,
99
+ as in `photon.projects.agent_profile.get`. Each method takes one input model from
100
+ `photon_api.rpc_generated` (for example `CountProjectsInput`) with
101
+ `path`, `query`, `body` and header members as the operation declares.
102
+ - The normal facade returns the response model. `photon.raw` has the same methods
103
+ and returns a `RawResponse` with `data`, `status`, `headers` and `request_id`
104
+ (the `x-request-id` response header when present).
105
+ - Close the client to release connections: use `with` / `async with`, or call
106
+ `close()` / `await close()`. A client you pass in with `client=` is not closed
107
+ for you.
108
+
109
+ Request inputs and responses are Pydantic models typed by the contract.
110
+ Building an input model checks types and required fields and raises
111
+ `pydantic.ValidationError` if they do not match; limits such as patterns,
112
+ lengths and ranges are checked by the API. Successful responses are parsed into
113
+ the model declared for their status. Fields the SDK does not know are kept, and
114
+ enums are open (`Literal["a", "b"] | str`), so the client keeps working when the
115
+ API adds fields or values. Dates and date-times are strings, as sent.
116
+
117
+ ### Model fields
118
+
119
+ Omit optional fields that you do not want to send. Pass `None` only when the
120
+ schema permits null. Optional fields without defaults use Pydantic's `MISSING`
121
+ sentinel; check `model_fields_set` to see which fields were supplied. Request
122
+ serialization excludes omitted fields while preserving explicit nulls where
123
+ allowed. Extra fields are retained and sent.
124
+
125
+ The pinned Pydantic release marks `MISSING` experimental. Models containing it
126
+ cannot be pickled, and static type checker support is limited. JSON serialization
127
+ is supported; use `model_dump(mode="json", by_alias=True, exclude_unset=True)`.
128
+
129
+ ## Errors
130
+
131
+ All errors extend `PhotonError`, which carries `operation_id` and `request_id`
132
+ when known.
133
+
134
+ | Class | Raised when | Attributes |
135
+ | --- | --- | --- |
136
+ | `ApiError` | The API returns a non-success status. The message is the problem `detail` when present. | `status`, `headers`, `body` (parsed JSON, or `None`), `raw_body`, `operation_id`, `request_id` |
137
+ | `ResponseValidationError` | A successful response has an undocumented status, is not empty when it should be, or does not match its model. | `status`, `raw_body`, `operation_id`, `request_id` |
138
+ | `TransportError` | The request failed at the HTTP layer (connection error, timeout) on the last attempt. | `operation_id`; the `httpx` error is in `__cause__` |
139
+
140
+ ```python
141
+ from photon_api import ApiError
142
+
143
+ try:
144
+ photon.organizations.projects.count(request)
145
+ except ApiError as error:
146
+ print(error.status, error.request_id, error.body)
147
+ raise
148
+ ```
149
+
150
+ ## Retries and timeouts
151
+
152
+ | Argument | Default | Behavior |
153
+ | --- | --- | --- |
154
+ | `timeout` | `30.0` | Seconds, applied to each attempt. |
155
+ | `max_attempts` | `3` | Total attempts, including the first. Values are clamped to 1 to 3; `1` disables retries. |
156
+ | `max_retry_after` | `60.0` | Longest `Retry-After`, in seconds, the client waits for. |
157
+
158
+ - Only `GET` operations and requests that carry an `Idempotency-Key` header
159
+ (from the input or from the `headers` option) are retried. Other mutations are
160
+ sent once.
161
+ - Retryable requests are retried after status 408, 429, 502, 503 or 504, or after
162
+ an `httpx` error (for example a connection error or timeout). The status list
163
+ and backoff are not configurable.
164
+ - Without `Retry-After`, the delay is random between 0 and
165
+ `min(2.0, 0.25 * 2^(attempt - 1))` seconds.
166
+ - `Retry-After` (seconds or an HTTP date) is used as the delay. If it exceeds
167
+ `max_retry_after`, the client stops retrying and handles that response (an
168
+ `ApiError` for an error status).
169
+ - When attempts run out, the last response is handled normally, or
170
+ `TransportError` is raised.
171
+
172
+ You can pass your own `httpx.Client` (for `Photon`) or `httpx.AsyncClient` (for
173
+ `AsyncPhoton`) as `client=`; the base URL, `timeout` and retry behavior above
174
+ still apply.
175
+
176
+ ## Base URL
177
+
178
+ The client uses `https://api.photon.codes`. For tests and alternative environments,
179
+ set `base_url`:
180
+
181
+ ```python
182
+ photon = Photon(
183
+ base_url="http://localhost:8080",
184
+ headers={"Authorization": f"Bearer {token}"},
185
+ )
186
+ ```
187
+
188
+ ## API reference
189
+
190
+ The OpenAPI contract this client is generated from is
191
+ [`openapi/openapi.json`](https://github.com/photon-hq/api/blob/main/openapi/openapi.json)
192
+ in [photon-hq/api](https://github.com/photon-hq/api), with a Postman
193
+ collection generated from it. The same repository contains the
194
+ [TypeScript](https://github.com/photon-hq/api/tree/main/packages/typescript),
195
+ [Python](https://github.com/photon-hq/api/tree/main/packages/python) and
196
+ [Rust](https://github.com/photon-hq/api/tree/main/packages/rust) clients.
197
+
198
+ ## Versioning
199
+
200
+ The TypeScript, Python and Rust clients share one version and are released
201
+ together. Releases follow semantic versioning, starting at 0.1.0. Before 1.0,
202
+ breaking changes increase the minor version. Changes are listed in
203
+ [CHANGELOG.md](https://github.com/photon-hq/api/blob/main/CHANGELOG.md).
204
+
205
+ ## Building from source
206
+
207
+ Generation requires Node.js 26 and no API credentials; Node.js is not needed to
208
+ use the package. From the repository root:
209
+
210
+ ```sh
211
+ python -m pip install -r tools/python-codegen/requirements-dev.txt
212
+ npm ci
213
+ npm run regenerate:python
214
+ python -m pip install -e packages/python
215
+ npm run test:python
216
+ ```
217
+
218
+ CI regenerates the client from the committed contract and fails if the output
219
+ differs from the committed files.
220
+
221
+ ## Support and security
222
+
223
+ Report bugs and requests through
224
+ [GitHub issues](https://github.com/photon-hq/api/issues). Do not include
225
+ credentials or private data. Report vulnerabilities privately as described in
226
+ [SECURITY.md](https://github.com/photon-hq/api/blob/main/SECURITY.md).
227
+
228
+ This repository is generated, so external pull requests are not accepted. See
229
+ [CONTRIBUTING.md](https://github.com/photon-hq/api/blob/main/CONTRIBUTING.md).
230
+
231
+ ## License
232
+
233
+ MIT. See [LICENSE](https://github.com/photon-hq/api/blob/main/LICENSE) and
234
+ [NOTICE](https://github.com/photon-hq/api/blob/main/NOTICE).
@@ -0,0 +1,220 @@
1
+ # photonhq-api
2
+
3
+ Synchronous and asynchronous Python client for the Photon API, generated from the
4
+ public OpenAPI contract. Requires Python 3.11+ and imports as `photon_api`.
5
+
6
+ > **Preview release.** This version is generated from the current production
7
+ > contract before all of its schemas have stable names. Types the contract
8
+ > does not name yet have names derived from their operation (for example
9
+ > `ListProjectsResponse200ApplicationJson`) or carry a version prefix such as
10
+ > `Photon20260701_`. These type names change in 0.2.0, when the contract
11
+ > names them; the rename does not change requests, responses or method names.
12
+
13
+ ## Installation
14
+
15
+ ```sh
16
+ pip install photonhq-api
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ ```python
22
+ import os
23
+
24
+ from photon_api import Photon
25
+ from photon_api.rpc_generated import CountProjectsInput
26
+
27
+ token = os.environ["PHOTON_API_TOKEN"]
28
+ request = CountProjectsInput.model_validate(
29
+ {"path": {"organizationId": os.environ["PHOTON_ORGANIZATION_ID"]}}
30
+ )
31
+
32
+ with Photon(headers={"Authorization": f"Bearer {token}"}) as photon:
33
+ result = photon.organizations.projects.count(request)
34
+ print(result.count)
35
+ ```
36
+
37
+ `countProjects` accepts an Account Service Key or an Organization Service Identity
38
+ API key / M2M access token. Set `PHOTON_API_TOKEN` to one of those credentials
39
+ and `PHOTON_ORGANIZATION_ID` to the ID of an organization it can access. See the
40
+ Photon dashboard and documentation to create credentials and find IDs.
41
+
42
+ ## Authentication
43
+
44
+ Every credential is sent as `Authorization: Bearer <credential>`. Pass it through
45
+ the `headers` option. The client does not choose or check a credential type; the
46
+ API accepts or rejects it.
47
+
48
+ | Credential | Security scheme | Notes |
49
+ | --- | --- | --- |
50
+ | Account Service Key (`pho_ask_...`) | `accountServiceKey` | Acts as the account and reaches every route the account can. |
51
+ | Project API key (`pho_sk_...`) | `projectApiKey` | Bound to one project. Accepted only under `/v1/projects/{projectId}`. |
52
+ | Organization Service Identity API key or M2M access token | `serviceIdentityBearer` | Restricted to its organization and explicitly granted permissions. Never send an M2M client secret to an API endpoint. |
53
+ | OAuth access token | `oauth2` | Scope is intersected with the permissions the route grants; an empty intersection returns `403 insufficient_scope`. |
54
+
55
+ Each operation's `security` entry in the OpenAPI contract lists the credential
56
+ types it accepts. A few operations accept requests without credentials.
57
+
58
+ The SDK does not run OAuth flows. Obtain OAuth access tokens and manage refresh
59
+ in your application's own authentication flow, then supply the current token.
60
+
61
+ `headers` accepts a mapping or a callable returning one. The callable runs before
62
+ every attempt, including retries, so it can return a freshly refreshed token.
63
+ For `AsyncPhoton` the callable may be async; for `Photon` it must be synchronous.
64
+
65
+ ## Using the client
66
+
67
+ ```python
68
+ import asyncio
69
+
70
+ from photon_api import AsyncPhoton
71
+
72
+
73
+ async def main() -> None:
74
+ async with AsyncPhoton(headers={"Authorization": f"Bearer {token}"}) as photon:
75
+ result = await photon.organizations.projects.count(request)
76
+ print(result.count)
77
+
78
+
79
+ asyncio.run(main())
80
+ ```
81
+
82
+ - `Photon` is synchronous; `AsyncPhoton` has the same methods as coroutines.
83
+ - Operations are grouped into namespaces that follow the API paths, for example
84
+ `photon.organizations.projects.count`. Namespaces and methods are snake_case,
85
+ as in `photon.projects.agent_profile.get`. Each method takes one input model from
86
+ `photon_api.rpc_generated` (for example `CountProjectsInput`) with
87
+ `path`, `query`, `body` and header members as the operation declares.
88
+ - The normal facade returns the response model. `photon.raw` has the same methods
89
+ and returns a `RawResponse` with `data`, `status`, `headers` and `request_id`
90
+ (the `x-request-id` response header when present).
91
+ - Close the client to release connections: use `with` / `async with`, or call
92
+ `close()` / `await close()`. A client you pass in with `client=` is not closed
93
+ for you.
94
+
95
+ Request inputs and responses are Pydantic models typed by the contract.
96
+ Building an input model checks types and required fields and raises
97
+ `pydantic.ValidationError` if they do not match; limits such as patterns,
98
+ lengths and ranges are checked by the API. Successful responses are parsed into
99
+ the model declared for their status. Fields the SDK does not know are kept, and
100
+ enums are open (`Literal["a", "b"] | str`), so the client keeps working when the
101
+ API adds fields or values. Dates and date-times are strings, as sent.
102
+
103
+ ### Model fields
104
+
105
+ Omit optional fields that you do not want to send. Pass `None` only when the
106
+ schema permits null. Optional fields without defaults use Pydantic's `MISSING`
107
+ sentinel; check `model_fields_set` to see which fields were supplied. Request
108
+ serialization excludes omitted fields while preserving explicit nulls where
109
+ allowed. Extra fields are retained and sent.
110
+
111
+ The pinned Pydantic release marks `MISSING` experimental. Models containing it
112
+ cannot be pickled, and static type checker support is limited. JSON serialization
113
+ is supported; use `model_dump(mode="json", by_alias=True, exclude_unset=True)`.
114
+
115
+ ## Errors
116
+
117
+ All errors extend `PhotonError`, which carries `operation_id` and `request_id`
118
+ when known.
119
+
120
+ | Class | Raised when | Attributes |
121
+ | --- | --- | --- |
122
+ | `ApiError` | The API returns a non-success status. The message is the problem `detail` when present. | `status`, `headers`, `body` (parsed JSON, or `None`), `raw_body`, `operation_id`, `request_id` |
123
+ | `ResponseValidationError` | A successful response has an undocumented status, is not empty when it should be, or does not match its model. | `status`, `raw_body`, `operation_id`, `request_id` |
124
+ | `TransportError` | The request failed at the HTTP layer (connection error, timeout) on the last attempt. | `operation_id`; the `httpx` error is in `__cause__` |
125
+
126
+ ```python
127
+ from photon_api import ApiError
128
+
129
+ try:
130
+ photon.organizations.projects.count(request)
131
+ except ApiError as error:
132
+ print(error.status, error.request_id, error.body)
133
+ raise
134
+ ```
135
+
136
+ ## Retries and timeouts
137
+
138
+ | Argument | Default | Behavior |
139
+ | --- | --- | --- |
140
+ | `timeout` | `30.0` | Seconds, applied to each attempt. |
141
+ | `max_attempts` | `3` | Total attempts, including the first. Values are clamped to 1 to 3; `1` disables retries. |
142
+ | `max_retry_after` | `60.0` | Longest `Retry-After`, in seconds, the client waits for. |
143
+
144
+ - Only `GET` operations and requests that carry an `Idempotency-Key` header
145
+ (from the input or from the `headers` option) are retried. Other mutations are
146
+ sent once.
147
+ - Retryable requests are retried after status 408, 429, 502, 503 or 504, or after
148
+ an `httpx` error (for example a connection error or timeout). The status list
149
+ and backoff are not configurable.
150
+ - Without `Retry-After`, the delay is random between 0 and
151
+ `min(2.0, 0.25 * 2^(attempt - 1))` seconds.
152
+ - `Retry-After` (seconds or an HTTP date) is used as the delay. If it exceeds
153
+ `max_retry_after`, the client stops retrying and handles that response (an
154
+ `ApiError` for an error status).
155
+ - When attempts run out, the last response is handled normally, or
156
+ `TransportError` is raised.
157
+
158
+ You can pass your own `httpx.Client` (for `Photon`) or `httpx.AsyncClient` (for
159
+ `AsyncPhoton`) as `client=`; the base URL, `timeout` and retry behavior above
160
+ still apply.
161
+
162
+ ## Base URL
163
+
164
+ The client uses `https://api.photon.codes`. For tests and alternative environments,
165
+ set `base_url`:
166
+
167
+ ```python
168
+ photon = Photon(
169
+ base_url="http://localhost:8080",
170
+ headers={"Authorization": f"Bearer {token}"},
171
+ )
172
+ ```
173
+
174
+ ## API reference
175
+
176
+ The OpenAPI contract this client is generated from is
177
+ [`openapi/openapi.json`](https://github.com/photon-hq/api/blob/main/openapi/openapi.json)
178
+ in [photon-hq/api](https://github.com/photon-hq/api), with a Postman
179
+ collection generated from it. The same repository contains the
180
+ [TypeScript](https://github.com/photon-hq/api/tree/main/packages/typescript),
181
+ [Python](https://github.com/photon-hq/api/tree/main/packages/python) and
182
+ [Rust](https://github.com/photon-hq/api/tree/main/packages/rust) clients.
183
+
184
+ ## Versioning
185
+
186
+ The TypeScript, Python and Rust clients share one version and are released
187
+ together. Releases follow semantic versioning, starting at 0.1.0. Before 1.0,
188
+ breaking changes increase the minor version. Changes are listed in
189
+ [CHANGELOG.md](https://github.com/photon-hq/api/blob/main/CHANGELOG.md).
190
+
191
+ ## Building from source
192
+
193
+ Generation requires Node.js 26 and no API credentials; Node.js is not needed to
194
+ use the package. From the repository root:
195
+
196
+ ```sh
197
+ python -m pip install -r tools/python-codegen/requirements-dev.txt
198
+ npm ci
199
+ npm run regenerate:python
200
+ python -m pip install -e packages/python
201
+ npm run test:python
202
+ ```
203
+
204
+ CI regenerates the client from the committed contract and fails if the output
205
+ differs from the committed files.
206
+
207
+ ## Support and security
208
+
209
+ Report bugs and requests through
210
+ [GitHub issues](https://github.com/photon-hq/api/issues). Do not include
211
+ credentials or private data. Report vulnerabilities privately as described in
212
+ [SECURITY.md](https://github.com/photon-hq/api/blob/main/SECURITY.md).
213
+
214
+ This repository is generated, so external pull requests are not accepted. See
215
+ [CONTRIBUTING.md](https://github.com/photon-hq/api/blob/main/CONTRIBUTING.md).
216
+
217
+ ## License
218
+
219
+ MIT. See [LICENSE](https://github.com/photon-hq/api/blob/main/LICENSE) and
220
+ [NOTICE](https://github.com/photon-hq/api/blob/main/NOTICE).
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling==1.31.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "photonhq-api"
7
+ version = "0.1.0"
8
+ description = "Generated Photon API RPC client"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "Photon" }]
13
+ dependencies = [
14
+ "email-validator>=2.3.0,<3",
15
+ "httpx>=0.28.1,<1",
16
+ # Generated models use Pydantic's experimental MISSING sentinel, which may
17
+ # change in a minor release.
18
+ "pydantic>=2.13.5,<2.14",
19
+ ]
20
+
21
+ [project.urls]
22
+ Repository = "https://github.com/photon-hq/api"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src/photon_api"]
26
+
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
29
+ addopts = "-q"
30
+
31
+ [tool.ruff]
32
+ target-version = "py311"
33
+ line-length = 100
34
+
35
+ [tool.ruff.lint]
36
+ select = ["E", "F", "I", "UP", "B", "SIM"]
37
+
38
+ [tool.ruff.lint.per-file-ignores]
39
+ "src/photon_api/generated/__init__.py" = ["F403"]
40
+ "src/photon_api/generated/models.py" = ["E501", "UP007", "UP037", "UP045"]
41
+ # Preserve authored OpenAPI prose in generated method docstrings.
42
+ "src/photon_api/rpc_generated.py" = ["E501"]
@@ -0,0 +1,18 @@
1
+ from .client import AsyncPhoton, Photon
2
+ from .errors import (
3
+ ApiError,
4
+ PhotonError,
5
+ ResponseValidationError,
6
+ TransportError,
7
+ )
8
+ from .transport import RawResponse
9
+
10
+ __all__ = [
11
+ "ApiError",
12
+ "AsyncPhoton",
13
+ "Photon",
14
+ "PhotonError",
15
+ "RawResponse",
16
+ "ResponseValidationError",
17
+ "TransportError",
18
+ ]
@@ -0,0 +1,62 @@
1
+ """Base classes and helpers of the generated models (tools/python-codegen).
2
+
3
+ The models carry the contract's types; values the contract restricts only by
4
+ validation keywords (patterns, lengths, bounds, sizes) are left to the service.
5
+ """
6
+
7
+ from typing import Annotated, Any, Generic, TypeVar
8
+
9
+ from pydantic import AfterValidator, ConfigDict, TypeAdapter
10
+ from pydantic import BaseModel as PydanticBaseModel
11
+ from pydantic import RootModel as PydanticRootModel
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ class BaseModel(PydanticBaseModel):
17
+ # Members the SDK does not know are kept (a response may gain fields).
18
+ model_config = ConfigDict(extra="allow", defer_build=True)
19
+
20
+
21
+ class RootModel(PydanticRootModel[T], Generic[T]):
22
+ model_config = ConfigDict(defer_build=True)
23
+
24
+
25
+ def validate_all_of(data: Any, *constraints: type[PydanticBaseModel]) -> Any:
26
+ """Check the input of a union narrowed by object constraints against each constraint.
27
+
28
+ Generated (tools/python-codegen/intersections.py) as a `mode="before"`
29
+ validator: every constraint model validates the value as given, and the
30
+ root type validates the union; the value is valid only if all accept it.
31
+ A model instance is checked through its JSON form.
32
+ """
33
+ raw = (
34
+ data.model_dump(mode="json", by_alias=True) if isinstance(data, PydanticBaseModel) else data
35
+ )
36
+ for constraint in constraints:
37
+ constraint.model_validate(raw)
38
+ return data
39
+
40
+
41
+ class PrefixItems:
42
+ """`PrefixItems[T1, ..., Tn]`: a JSON Schema `prefixItems` array without `items`.
43
+
44
+ Item i, when present, validates as Ti; later items are unconstrained, and
45
+ the array may be shorter than n. Generated for arrays whose positional
46
+ items the model generator would otherwise drop
47
+ (tools/python-codegen/prefix_items.py). The value is a `list`.
48
+ """
49
+
50
+ def __class_getitem__(cls, params: Any) -> Any:
51
+ types = params if isinstance(params, tuple) else (params,)
52
+ adapters: list[TypeAdapter[Any]] = []
53
+
54
+ def validate(value: list[Any]) -> list[Any]:
55
+ if not adapters:
56
+ adapters.extend(TypeAdapter(item) for item in types)
57
+ return [
58
+ adapters[index].validate_python(item) if index < len(adapters) else item
59
+ for index, item in enumerate(value)
60
+ ]
61
+
62
+ return Annotated[list[Any], AfterValidator(validate)]