flares-client 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,36 @@
1
+ # Generated by Cargo
2
+ # will have compiled files and executables
3
+ debug
4
+ target
5
+
6
+ # These are backup files generated by rustfmt
7
+ **/*.rs.bk
8
+
9
+ # MSVC Windows builds of rustc generate these, which store debugging information
10
+ *.pdb
11
+
12
+ # Generated by cargo mutants
13
+ # Contains mutation testing data
14
+ **/mutants.out*/
15
+
16
+ # RustRover
17
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
18
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
19
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
20
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
21
+ #.idea/
22
+
23
+ # Local runtime/configuration files
24
+ .venv/
25
+ *.sqlite3
26
+ *.sqlite3-shm
27
+ *.sqlite3-wal
28
+ /server.yaml
29
+ /client.yaml
30
+
31
+ # Python and release artifacts
32
+ __pycache__/
33
+ *.py[cod]
34
+ .pytest_cache/
35
+ .ruff_cache/
36
+ dist/
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.5
2
+ Name: flares-client
3
+ Version: 0.1.0
4
+ Summary: Synchronous and asynchronous HTTP clients for Flares
5
+ Project-URL: Repository, https://github.com/torpedro/flares
6
+ Project-URL: Issues, https://github.com/torpedro/flares/issues
7
+ Project-URL: Documentation, https://github.com/torpedro/flares/blob/main/clients/python/README.md
8
+ License-Expression: MIT
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: httpx<1,>=0.28
11
+ Requires-Dist: pydantic<3,>=2.10
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Flares Python client
15
+
16
+ Requires Python 3.11+. Install the built wheel with `pip install flares_client-*.whl`, or install from this directory with `uv pip install .`.
17
+
18
+ ```python
19
+ from flares_client import Client
20
+
21
+ with Client("http://localhost:8000", "your-api-token") as client:
22
+ result = client.open_issue("backup", title="Backup failed", severity="critical")
23
+ print(result.issue.status, result.notification.status)
24
+ client.close_issue("backup")
25
+ ```
26
+
27
+ ```python
28
+ from flares_client import AsyncClient
29
+
30
+
31
+ async def notify():
32
+ async with AsyncClient("http://localhost:8000", "your-api-token") as client:
33
+ return await client.alert("Backup", "Finished", idempotency_key="backup-run-123")
34
+ ```
35
+
36
+ Both clients have the same operations: `open_issue`, `close_issue`, `get_issue`, `list_issues`, `alert`, `get_delivery`, `register_heartbeat`, `check_in`, `list_heartbeats`, `delete_heartbeat`, `health`, `readiness`, and `metrics`. Responses are typed Pydantic models, except `metrics` (text) and `delete_heartbeat` (`None`). Models and enums are exported from `flares_client`. Request options match the HTTP API; list options are `status`, `limit=100`, and `offset=0`.
37
+
38
+ Use `timeout=15.0` to customize HTTPX's network-operation timeouts. Keep a client open to reuse connections; context managers close it, or call `close()` / `await aclose()` explicitly. Library configuration is passed directly; the server and CLI retain their YAML configuration.
39
+
40
+ Catch `ValidationError`, `TransportError` (with `timed_out`), `HTTPError` (with `status_code`), or `DecodeError`; all inherit `FlaresError`. Error messages do not include secrets or provider response bodies. A successful HTTP response with notification status `failed` is a normal result, not an exception. `pending` means accepted, not delivered. Requests never retry or follow redirects automatically. Reuse an explicit alert idempotency key after an uncertain outcome.
41
+
42
+ Development (from this directory): `uv sync --locked`, `uv run pytest tests`, `uv build`. Run the repository contract suite after building the server and Rust contract driver; see the root README.
@@ -0,0 +1,29 @@
1
+ # Flares Python client
2
+
3
+ Requires Python 3.11+. Install the built wheel with `pip install flares_client-*.whl`, or install from this directory with `uv pip install .`.
4
+
5
+ ```python
6
+ from flares_client import Client
7
+
8
+ with Client("http://localhost:8000", "your-api-token") as client:
9
+ result = client.open_issue("backup", title="Backup failed", severity="critical")
10
+ print(result.issue.status, result.notification.status)
11
+ client.close_issue("backup")
12
+ ```
13
+
14
+ ```python
15
+ from flares_client import AsyncClient
16
+
17
+
18
+ async def notify():
19
+ async with AsyncClient("http://localhost:8000", "your-api-token") as client:
20
+ return await client.alert("Backup", "Finished", idempotency_key="backup-run-123")
21
+ ```
22
+
23
+ Both clients have the same operations: `open_issue`, `close_issue`, `get_issue`, `list_issues`, `alert`, `get_delivery`, `register_heartbeat`, `check_in`, `list_heartbeats`, `delete_heartbeat`, `health`, `readiness`, and `metrics`. Responses are typed Pydantic models, except `metrics` (text) and `delete_heartbeat` (`None`). Models and enums are exported from `flares_client`. Request options match the HTTP API; list options are `status`, `limit=100`, and `offset=0`.
24
+
25
+ Use `timeout=15.0` to customize HTTPX's network-operation timeouts. Keep a client open to reuse connections; context managers close it, or call `close()` / `await aclose()` explicitly. Library configuration is passed directly; the server and CLI retain their YAML configuration.
26
+
27
+ Catch `ValidationError`, `TransportError` (with `timed_out`), `HTTPError` (with `status_code`), or `DecodeError`; all inherit `FlaresError`. Error messages do not include secrets or provider response bodies. A successful HTTP response with notification status `failed` is a normal result, not an exception. `pending` means accepted, not delivered. Requests never retry or follow redirects automatically. Reuse an explicit alert idempotency key after an uncertain outcome.
28
+
29
+ Development (from this directory): `uv sync --locked`, `uv run pytest tests`, `uv build`. Run the repository contract suite after building the server and Rust contract driver; see the root README.
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27,<2"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "flares-client"
7
+ version = "0.1.0"
8
+ description = "Synchronous and asynchronous HTTP clients for Flares"
9
+ requires-python = ">=3.11"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ dependencies = ["httpx>=0.28,<1", "pydantic>=2.10,<3"]
13
+
14
+ [project.urls]
15
+ Repository = "https://github.com/torpedro/flares"
16
+ Issues = "https://github.com/torpedro/flares/issues"
17
+ Documentation = "https://github.com/torpedro/flares/blob/main/clients/python/README.md"
18
+
19
+ [dependency-groups]
20
+ dev = ["pytest>=8,<10", "ruff>=0.11,<1"]
21
+
22
+ [tool.pytest.ini_options]
23
+ testpaths = ["tests", "../../tests/contract"]
24
+
25
+ [tool.ruff]
26
+ target-version = "py311"
27
+ line-length = 100
28
+
29
+ [tool.ruff.lint]
30
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,47 @@
1
+ """Flares client library. HTTP success and notification delivery are separate outcomes."""
2
+
3
+ from .client import AsyncClient, Client
4
+ from .errors import DecodeError, FlaresError, HTTPError, TransportError, ValidationError
5
+ from .models import (
6
+ Alert,
7
+ AlertResult,
8
+ Delivery,
9
+ DestinationOutcome,
10
+ Health,
11
+ Heartbeat,
12
+ HeartbeatInput,
13
+ Issue,
14
+ IssueList,
15
+ IssueStatus,
16
+ MutationResult,
17
+ Notification,
18
+ NotificationStatus,
19
+ OpenIssue,
20
+ Severity,
21
+ )
22
+
23
+ __version__ = "0.1.0"
24
+ __all__ = [
25
+ "Client",
26
+ "AsyncClient",
27
+ "FlaresError",
28
+ "ValidationError",
29
+ "TransportError",
30
+ "HTTPError",
31
+ "DecodeError",
32
+ "Alert",
33
+ "AlertResult",
34
+ "Delivery",
35
+ "DestinationOutcome",
36
+ "Health",
37
+ "Heartbeat",
38
+ "HeartbeatInput",
39
+ "Issue",
40
+ "IssueList",
41
+ "IssueStatus",
42
+ "MutationResult",
43
+ "Notification",
44
+ "NotificationStatus",
45
+ "OpenIssue",
46
+ "Severity",
47
+ ]
@@ -0,0 +1,94 @@
1
+ import math
2
+ from typing import Any
3
+
4
+ import httpx
5
+ from pydantic import BaseModel, TypeAdapter
6
+ from pydantic import ValidationError as ModelError
7
+
8
+ from .errors import DecodeError, HTTPError, ValidationError
9
+
10
+
11
+ def settings(base_url: str, token: str, timeout: float) -> tuple[str, dict[str, str]]:
12
+ try:
13
+ url = httpx.URL(base_url)
14
+ except (httpx.InvalidURL, TypeError):
15
+ raise ValidationError("Invalid API base URL") from None
16
+ if (
17
+ url.scheme not in ("http", "https")
18
+ or not url.host
19
+ or url.username
20
+ or url.password
21
+ # HTTPX retains empty delimiters even when query/fragment values are empty.
22
+ or "?" in str(url)
23
+ or "#" in str(url)
24
+ ):
25
+ raise ValidationError(
26
+ "API base URL must be HTTP(S) without credentials, query, or fragment"
27
+ )
28
+ if not isinstance(token, str) or not token or not all(33 <= ord(c) <= 126 for c in token):
29
+ raise ValidationError("API token must be nonempty printable ASCII without spaces")
30
+ if (
31
+ not isinstance(timeout, (float, int))
32
+ or isinstance(timeout, bool)
33
+ or not math.isfinite(timeout)
34
+ or not 0 < timeout <= 86400
35
+ ):
36
+ raise ValidationError("Timeout must be greater than zero and at most 86400 seconds")
37
+ return str(url).rstrip("/"), {"Authorization": f"Bearer {token}"}
38
+
39
+
40
+ def body(model: type[BaseModel], **values: Any) -> dict[str, Any]:
41
+ try:
42
+ return model.model_validate(values).model_dump(mode="json")
43
+ except ModelError as error:
44
+ fields = sorted({str(e["loc"][0]) for e in error.errors() if e["loc"]})
45
+ raise ValidationError(f"Invalid request fields: {', '.join(fields)}") from None
46
+
47
+
48
+ def key_header(key: str | None) -> dict[str, str]:
49
+ if key is None:
50
+ return {}
51
+ if (
52
+ not isinstance(key, str)
53
+ or not 1 <= len(key) <= 200
54
+ or not all(33 <= ord(c) <= 126 for c in key)
55
+ ):
56
+ raise ValidationError("Invalid idempotency key")
57
+ return {"Idempotency-Key": key}
58
+
59
+
60
+ def listing(status: str | None, limit: int, offset: int) -> dict[str, Any]:
61
+ if (
62
+ status not in (None, "open", "closed")
63
+ or type(limit) is not int
64
+ or not 1 <= limit <= 1000
65
+ or type(offset) is not int
66
+ or not 0 <= offset <= 4_294_967_295
67
+ ):
68
+ raise ValidationError("Invalid status, limit, or offset")
69
+ params = {"limit": limit, "offset": offset}
70
+ if status is not None:
71
+ params["status"] = status
72
+ return params
73
+
74
+
75
+ def delivery_path(id: int) -> str:
76
+ if type(id) is not int or not 1 <= id <= 9_223_372_036_854_775_807:
77
+ raise ValidationError("delivery id must be a positive 64-bit integer")
78
+ return f"/v1/deliveries/{id}"
79
+
80
+
81
+ def decode(response: httpx.Response, model: Any) -> Any:
82
+ if not response.is_success:
83
+ raise HTTPError(response.status_code)
84
+ if model is str:
85
+ return response.text
86
+ try:
87
+ value = response.json()
88
+ if model is None:
89
+ if not isinstance(value, dict) or value.get("deleted") is not True:
90
+ raise ValueError
91
+ return None
92
+ return TypeAdapter(model).validate_python(value)
93
+ except (ValueError, TypeError):
94
+ raise DecodeError() from None
@@ -0,0 +1,269 @@
1
+ """HTTP clients with identical synchronous and asynchronous operations."""
2
+
3
+ from typing import Any, Self
4
+
5
+ import httpx
6
+
7
+ from ._common import body, decode, delivery_path, key_header, listing, settings
8
+ from .errors import TransportError
9
+ from .models import (
10
+ Alert,
11
+ AlertResult,
12
+ CloseIssue,
13
+ Delivery,
14
+ Health,
15
+ Heartbeat,
16
+ HeartbeatInput,
17
+ Issue,
18
+ IssueList,
19
+ IssueStatus,
20
+ MutationResult,
21
+ OpenIssue,
22
+ Severity,
23
+ )
24
+
25
+
26
+ class Client:
27
+ def __init__(self, base_url: str, token: str, *, timeout: float = 15.0):
28
+ self._base_url, headers = settings(base_url, token, timeout)
29
+ self._client = httpx.Client(headers=headers, timeout=timeout, follow_redirects=False)
30
+
31
+ def __enter__(self) -> Self:
32
+ self._client.__enter__()
33
+ return self
34
+
35
+ def __exit__(self, *args: Any) -> None:
36
+ self._client.__exit__(*args)
37
+
38
+ def close(self) -> None:
39
+ self._client.close()
40
+
41
+ def _request(self, method: str, path: str, model: Any, **kwargs: Any) -> Any:
42
+ try:
43
+ response = self._client.request(method, self._base_url + path, **kwargs)
44
+ except httpx.HTTPError as error:
45
+ raise TransportError(timed_out=isinstance(error, httpx.TimeoutException)) from None
46
+ return decode(response, model)
47
+
48
+ def open_issue(
49
+ self,
50
+ id: str,
51
+ *,
52
+ title: str | None = None,
53
+ message: str | None = None,
54
+ severity: Severity | str = Severity.WARNING,
55
+ remind_every_seconds: int | None = None,
56
+ notify_on_resolution: bool = False,
57
+ ) -> MutationResult:
58
+ data = body(
59
+ OpenIssue,
60
+ id=id,
61
+ title=title,
62
+ message=message,
63
+ severity=severity,
64
+ remind_every_seconds=remind_every_seconds,
65
+ notify_on_resolution=notify_on_resolution,
66
+ )
67
+ return self._request("POST", "/v1/issues/open", MutationResult, json=data)
68
+
69
+ def close_issue(self, id: str) -> MutationResult:
70
+ return self._request(
71
+ "POST", "/v1/issues/close", MutationResult, json=body(CloseIssue, id=id)
72
+ )
73
+
74
+ def get_issue(self, id: str) -> Issue:
75
+ return self._request("GET", "/v1/issue", Issue, params=body(CloseIssue, id=id))
76
+
77
+ def list_issues(
78
+ self, *, status: IssueStatus | str | None = None, limit: int = 100, offset: int = 0
79
+ ) -> IssueList:
80
+ return self._request("GET", "/v1/issues", IssueList, params=listing(status, limit, offset))
81
+
82
+ def alert(
83
+ self,
84
+ title: str,
85
+ message: str,
86
+ *,
87
+ severity: Severity | str = Severity.WARNING,
88
+ group_key: str | None = None,
89
+ idempotency_key: str | None = None,
90
+ ) -> AlertResult:
91
+ return self._request(
92
+ "POST",
93
+ "/v1/alerts",
94
+ AlertResult,
95
+ json=body(Alert, title=title, message=message, severity=severity, group_key=group_key),
96
+ headers=key_header(idempotency_key),
97
+ )
98
+
99
+ def get_delivery(self, id: int) -> Delivery:
100
+ return self._request("GET", delivery_path(id), Delivery)
101
+
102
+ def register_heartbeat(
103
+ self,
104
+ id: str,
105
+ *,
106
+ title: str,
107
+ interval_seconds: int,
108
+ grace_seconds: int = 0,
109
+ severity: Severity | str = Severity.WARNING,
110
+ notify_on_recovery: bool = False,
111
+ ) -> Heartbeat:
112
+ return self._request(
113
+ "POST",
114
+ "/v1/heartbeats",
115
+ Heartbeat,
116
+ json=body(
117
+ HeartbeatInput,
118
+ id=id,
119
+ title=title,
120
+ interval_seconds=interval_seconds,
121
+ grace_seconds=grace_seconds,
122
+ severity=severity,
123
+ notify_on_recovery=notify_on_recovery,
124
+ ),
125
+ )
126
+
127
+ def check_in(self, id: str) -> Heartbeat:
128
+ return self._request(
129
+ "POST", "/v1/heartbeats/check-in", Heartbeat, json=body(CloseIssue, id=id)
130
+ )
131
+
132
+ def list_heartbeats(self) -> list[Heartbeat]:
133
+ return self._request("GET", "/v1/heartbeats", list[Heartbeat])
134
+
135
+ def delete_heartbeat(self, id: str) -> None:
136
+ return self._request("DELETE", "/v1/heartbeat", None, params=body(CloseIssue, id=id))
137
+
138
+ def health(self) -> Health:
139
+ return self._request("GET", "/healthz", Health)
140
+
141
+ def readiness(self) -> Health:
142
+ return self._request("GET", "/readyz", Health)
143
+
144
+ def metrics(self) -> str:
145
+ return self._request("GET", "/metrics", str)
146
+
147
+
148
+ class AsyncClient:
149
+ def __init__(self, base_url: str, token: str, *, timeout: float = 15.0):
150
+ self._base_url, headers = settings(base_url, token, timeout)
151
+ self._client = httpx.AsyncClient(headers=headers, timeout=timeout, follow_redirects=False)
152
+
153
+ async def __aenter__(self) -> Self:
154
+ await self._client.__aenter__()
155
+ return self
156
+
157
+ async def __aexit__(self, *args: Any) -> None:
158
+ await self._client.__aexit__(*args)
159
+
160
+ async def aclose(self) -> None:
161
+ await self._client.aclose()
162
+
163
+ async def _request(self, method: str, path: str, model: Any, **kwargs: Any) -> Any:
164
+ try:
165
+ response = await self._client.request(method, self._base_url + path, **kwargs)
166
+ except httpx.HTTPError as error:
167
+ raise TransportError(timed_out=isinstance(error, httpx.TimeoutException)) from None
168
+ return decode(response, model)
169
+
170
+ async def open_issue(
171
+ self,
172
+ id: str,
173
+ *,
174
+ title: str | None = None,
175
+ message: str | None = None,
176
+ severity: Severity | str = Severity.WARNING,
177
+ remind_every_seconds: int | None = None,
178
+ notify_on_resolution: bool = False,
179
+ ) -> MutationResult:
180
+ data = body(
181
+ OpenIssue,
182
+ id=id,
183
+ title=title,
184
+ message=message,
185
+ severity=severity,
186
+ remind_every_seconds=remind_every_seconds,
187
+ notify_on_resolution=notify_on_resolution,
188
+ )
189
+ return await self._request("POST", "/v1/issues/open", MutationResult, json=data)
190
+
191
+ async def close_issue(self, id: str) -> MutationResult:
192
+ return await self._request(
193
+ "POST", "/v1/issues/close", MutationResult, json=body(CloseIssue, id=id)
194
+ )
195
+
196
+ async def get_issue(self, id: str) -> Issue:
197
+ return await self._request("GET", "/v1/issue", Issue, params=body(CloseIssue, id=id))
198
+
199
+ async def list_issues(
200
+ self, *, status: IssueStatus | str | None = None, limit: int = 100, offset: int = 0
201
+ ) -> IssueList:
202
+ return await self._request(
203
+ "GET", "/v1/issues", IssueList, params=listing(status, limit, offset)
204
+ )
205
+
206
+ async def alert(
207
+ self,
208
+ title: str,
209
+ message: str,
210
+ *,
211
+ severity: Severity | str = Severity.WARNING,
212
+ group_key: str | None = None,
213
+ idempotency_key: str | None = None,
214
+ ) -> AlertResult:
215
+ return await self._request(
216
+ "POST",
217
+ "/v1/alerts",
218
+ AlertResult,
219
+ json=body(Alert, title=title, message=message, severity=severity, group_key=group_key),
220
+ headers=key_header(idempotency_key),
221
+ )
222
+
223
+ async def get_delivery(self, id: int) -> Delivery:
224
+ return await self._request("GET", delivery_path(id), Delivery)
225
+
226
+ async def register_heartbeat(
227
+ self,
228
+ id: str,
229
+ *,
230
+ title: str,
231
+ interval_seconds: int,
232
+ grace_seconds: int = 0,
233
+ severity: Severity | str = Severity.WARNING,
234
+ notify_on_recovery: bool = False,
235
+ ) -> Heartbeat:
236
+ return await self._request(
237
+ "POST",
238
+ "/v1/heartbeats",
239
+ Heartbeat,
240
+ json=body(
241
+ HeartbeatInput,
242
+ id=id,
243
+ title=title,
244
+ interval_seconds=interval_seconds,
245
+ grace_seconds=grace_seconds,
246
+ severity=severity,
247
+ notify_on_recovery=notify_on_recovery,
248
+ ),
249
+ )
250
+
251
+ async def check_in(self, id: str) -> Heartbeat:
252
+ return await self._request(
253
+ "POST", "/v1/heartbeats/check-in", Heartbeat, json=body(CloseIssue, id=id)
254
+ )
255
+
256
+ async def list_heartbeats(self) -> list[Heartbeat]:
257
+ return await self._request("GET", "/v1/heartbeats", list[Heartbeat])
258
+
259
+ async def delete_heartbeat(self, id: str) -> None:
260
+ return await self._request("DELETE", "/v1/heartbeat", None, params=body(CloseIssue, id=id))
261
+
262
+ async def health(self) -> Health:
263
+ return await self._request("GET", "/healthz", Health)
264
+
265
+ async def readiness(self) -> Health:
266
+ return await self._request("GET", "/readyz", Health)
267
+
268
+ async def metrics(self) -> str:
269
+ return await self._request("GET", "/metrics", str)
@@ -0,0 +1,29 @@
1
+ """Sanitized, distinguishable client errors."""
2
+
3
+
4
+ class FlaresError(Exception):
5
+ """Base class for Flares client errors."""
6
+
7
+
8
+ class ValidationError(FlaresError, ValueError):
9
+ """Invalid client configuration or request arguments."""
10
+
11
+
12
+ class TransportError(FlaresError):
13
+ def __init__(self, *, timed_out: bool = False):
14
+ self.timed_out = timed_out
15
+ super().__init__(
16
+ "API request failed or timed out; delivery may have occurred. "
17
+ "Reuse the alert idempotency key or check issue state before retrying"
18
+ )
19
+
20
+
21
+ class HTTPError(FlaresError):
22
+ def __init__(self, status_code: int):
23
+ self.status_code = status_code
24
+ super().__init__(f"API returned HTTP {status_code}")
25
+
26
+
27
+ class DecodeError(FlaresError):
28
+ def __init__(self):
29
+ super().__init__("API returned an invalid response")
@@ -0,0 +1,140 @@
1
+ """Typed API models. Response objects accept additive server fields."""
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+ from typing import Annotated
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ IssueId = Annotated[str, Field(strict=True, min_length=1, max_length=200)]
10
+ Title = Annotated[str, Field(strict=True, min_length=1, max_length=250)]
11
+ Message = Annotated[str, Field(strict=True, min_length=1, max_length=1024)]
12
+ Interval = Annotated[int, Field(strict=True, ge=1, le=31_536_000)]
13
+
14
+
15
+ class Severity(StrEnum):
16
+ INFO = "info"
17
+ WARNING = "warning"
18
+ CRITICAL = "critical"
19
+
20
+
21
+ class IssueStatus(StrEnum):
22
+ OPEN = "open"
23
+ CLOSED = "closed"
24
+
25
+
26
+ class NotificationStatus(StrEnum):
27
+ PENDING = "pending"
28
+ SENT = "sent"
29
+ FAILED = "failed"
30
+ UNKNOWN = "unknown"
31
+ NOT_ATTEMPTED = "not_attempted"
32
+
33
+
34
+ class Request(BaseModel):
35
+ model_config = ConfigDict(extra="forbid", hide_input_in_errors=True)
36
+
37
+
38
+ class OpenIssue(Request):
39
+ id: IssueId
40
+ title: Title | None = None
41
+ message: Message | None = None
42
+ severity: Severity = Severity.WARNING
43
+ remind_every_seconds: Interval | None = None
44
+ notify_on_resolution: bool = False
45
+
46
+
47
+ class CloseIssue(Request):
48
+ id: IssueId
49
+
50
+
51
+ class Alert(Request):
52
+ title: Title
53
+ message: Message
54
+ severity: Severity = Severity.WARNING
55
+ group_key: IssueId | None = None
56
+
57
+
58
+ class HeartbeatInput(Request):
59
+ id: IssueId
60
+ title: Title
61
+ interval_seconds: Interval
62
+ grace_seconds: Annotated[int, Field(strict=True, ge=0, le=31_536_000)] = 0
63
+ severity: Severity = Severity.WARNING
64
+ notify_on_recovery: bool = False
65
+
66
+
67
+ class Notification(BaseModel):
68
+ status: NotificationStatus
69
+ error: str | None
70
+
71
+
72
+ class Issue(BaseModel):
73
+ id: str
74
+ status: IssueStatus
75
+ title: str
76
+ message: str
77
+ severity: Severity
78
+ remind_every_seconds: int | None
79
+ notify_on_resolution: bool
80
+ delivery_id: int | None
81
+ created_at: datetime
82
+ updated_at: datetime
83
+ opened_at: datetime
84
+ closed_at: datetime | None
85
+ opening_count: int
86
+ notification: Notification
87
+
88
+
89
+ class MutationResult(BaseModel):
90
+ delivery_id: int | None
91
+ issue: Issue
92
+ changed: bool
93
+ notification: Notification
94
+
95
+
96
+ class AlertResult(BaseModel):
97
+ delivery_id: int
98
+ notification: Notification
99
+
100
+
101
+ class IssueList(BaseModel):
102
+ items: list[Issue]
103
+ total: int
104
+ limit: int
105
+ offset: int
106
+
107
+
108
+ class Heartbeat(BaseModel):
109
+ id: str
110
+ title: str
111
+ interval_seconds: int
112
+ grace_seconds: int
113
+ severity: Severity
114
+ notify_on_recovery: bool
115
+ last_seen: int
116
+ due_at: int
117
+ overdue: bool
118
+
119
+
120
+ class DestinationOutcome(BaseModel):
121
+ destination: str
122
+ attempts: int
123
+ notification: Notification
124
+
125
+
126
+ class Delivery(BaseModel):
127
+ id: int
128
+ title: str
129
+ message: str
130
+ severity: Severity
131
+ kind: str
132
+ count: int
133
+ created_at: int
134
+ next_attempt_at: int
135
+ notification: Notification
136
+ destinations: list[DestinationOutcome]
137
+
138
+
139
+ class Health(BaseModel):
140
+ status: str
File without changes
@@ -0,0 +1,55 @@
1
+ import asyncio
2
+
3
+ import pytest
4
+ from flares_client import AsyncClient, Client, ValidationError
5
+
6
+
7
+ @pytest.mark.parametrize("client_type", [Client, AsyncClient])
8
+ @pytest.mark.parametrize(
9
+ "kwargs",
10
+ [
11
+ {"base_url": "ftp://example.test"},
12
+ {"base_url": "https://user:secret@example.test"},
13
+ {"base_url": "https://example.test?secret=value"},
14
+ {"base_url": "https://example.test?"},
15
+ {"base_url": "https://example.test#"},
16
+ {"base_url": "https://example.test/prefix?"},
17
+ {"base_url": "https://example.test/prefix#"},
18
+ {"token": "secret\nheader"},
19
+ {"timeout": 0},
20
+ {"timeout": float("nan")},
21
+ ],
22
+ )
23
+ def test_invalid_configuration(client_type, kwargs):
24
+ with pytest.raises(ValidationError) as error:
25
+ client_type(**{"base_url": "http://127.0.0.1:1", "token": "token", **kwargs})
26
+ assert "secret" not in str(error.value)
27
+
28
+
29
+ @pytest.mark.parametrize("asynchronous", [False, True])
30
+ @pytest.mark.parametrize(
31
+ "op,args",
32
+ [
33
+ ("open_issue", {"id": ""}),
34
+ ("open_issue", {"id": "a" * 201}),
35
+ ("open_issue", {"id": "ok", "remind_every_seconds": 0}),
36
+ ("list_issues", {"limit": 1001}),
37
+ ("list_issues", {"offset": -1}),
38
+ ("get_delivery", {"id": 0}),
39
+ ("alert", {"title": "x", "message": "x", "idempotency_key": "secret\n"}),
40
+ ("register_heartbeat", {"id": "x", "title": "x", "interval_seconds": 0}),
41
+ ],
42
+ )
43
+ def test_invalid_request_without_network(asynchronous, op, args):
44
+ if asynchronous:
45
+
46
+ async def check():
47
+ async with AsyncClient("http://127.0.0.1:1", "token") as client:
48
+ with pytest.raises(ValidationError):
49
+ await getattr(client, op)(**args)
50
+
51
+ asyncio.run(check())
52
+ else:
53
+ with Client("http://127.0.0.1:1", "token") as client:
54
+ with pytest.raises(ValidationError):
55
+ getattr(client, op)(**args)
@@ -0,0 +1,331 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.11"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.8.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.15.1"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "idna" },
20
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
21
+ ]
22
+ sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
23
+ wheels = [
24
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
25
+ ]
26
+
27
+ [[package]]
28
+ name = "certifi"
29
+ version = "2026.7.22"
30
+ source = { registry = "https://pypi.org/simple" }
31
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
32
+ wheels = [
33
+ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "colorama"
38
+ version = "0.4.6"
39
+ source = { registry = "https://pypi.org/simple" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
41
+ wheels = [
42
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
43
+ ]
44
+
45
+ [[package]]
46
+ name = "flares-client"
47
+ version = "0.1.0"
48
+ source = { editable = "." }
49
+ dependencies = [
50
+ { name = "httpx" },
51
+ { name = "pydantic" },
52
+ ]
53
+
54
+ [package.dev-dependencies]
55
+ dev = [
56
+ { name = "pytest" },
57
+ { name = "ruff" },
58
+ ]
59
+
60
+ [package.metadata]
61
+ requires-dist = [
62
+ { name = "httpx", specifier = ">=0.28,<1" },
63
+ { name = "pydantic", specifier = ">=2.10,<3" },
64
+ ]
65
+
66
+ [package.metadata.requires-dev]
67
+ dev = [
68
+ { name = "pytest", specifier = ">=8,<10" },
69
+ { name = "ruff", specifier = ">=0.11,<1" },
70
+ ]
71
+
72
+ [[package]]
73
+ name = "h11"
74
+ version = "0.16.0"
75
+ source = { registry = "https://pypi.org/simple" }
76
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
77
+ wheels = [
78
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
79
+ ]
80
+
81
+ [[package]]
82
+ name = "httpcore"
83
+ version = "1.0.9"
84
+ source = { registry = "https://pypi.org/simple" }
85
+ dependencies = [
86
+ { name = "certifi" },
87
+ { name = "h11" },
88
+ ]
89
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
90
+ wheels = [
91
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
92
+ ]
93
+
94
+ [[package]]
95
+ name = "httpx"
96
+ version = "0.28.1"
97
+ source = { registry = "https://pypi.org/simple" }
98
+ dependencies = [
99
+ { name = "anyio" },
100
+ { name = "certifi" },
101
+ { name = "httpcore" },
102
+ { name = "idna" },
103
+ ]
104
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
105
+ wheels = [
106
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
107
+ ]
108
+
109
+ [[package]]
110
+ name = "idna"
111
+ version = "3.19"
112
+ source = { registry = "https://pypi.org/simple" }
113
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
114
+ wheels = [
115
+ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
116
+ ]
117
+
118
+ [[package]]
119
+ name = "iniconfig"
120
+ version = "2.3.0"
121
+ source = { registry = "https://pypi.org/simple" }
122
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
123
+ wheels = [
124
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
125
+ ]
126
+
127
+ [[package]]
128
+ name = "packaging"
129
+ version = "26.3"
130
+ source = { registry = "https://pypi.org/simple" }
131
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
132
+ wheels = [
133
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
134
+ ]
135
+
136
+ [[package]]
137
+ name = "pluggy"
138
+ version = "1.6.0"
139
+ source = { registry = "https://pypi.org/simple" }
140
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
141
+ wheels = [
142
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
143
+ ]
144
+
145
+ [[package]]
146
+ name = "pydantic"
147
+ version = "2.13.5"
148
+ source = { registry = "https://pypi.org/simple" }
149
+ dependencies = [
150
+ { name = "annotated-types" },
151
+ { name = "pydantic-core" },
152
+ { name = "typing-extensions" },
153
+ { name = "typing-inspection" },
154
+ ]
155
+ sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" }
156
+ wheels = [
157
+ { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" },
158
+ ]
159
+
160
+ [[package]]
161
+ name = "pydantic-core"
162
+ version = "2.46.5"
163
+ source = { registry = "https://pypi.org/simple" }
164
+ dependencies = [
165
+ { name = "typing-extensions" },
166
+ ]
167
+ sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" }
168
+ wheels = [
169
+ { url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737, upload-time = "2026-08-28T09:57:57.711Z" },
170
+ { url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751, upload-time = "2026-08-28T09:57:59.265Z" },
171
+ { url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231, upload-time = "2026-08-28T09:58:00.678Z" },
172
+ { url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708, upload-time = "2026-08-28T09:58:02.267Z" },
173
+ { url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914, upload-time = "2026-08-28T09:58:03.934Z" },
174
+ { url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622, upload-time = "2026-08-28T09:58:05.516Z" },
175
+ { url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091, upload-time = "2026-08-28T09:58:07.135Z" },
176
+ { url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904, upload-time = "2026-08-28T09:58:08.814Z" },
177
+ { url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244, upload-time = "2026-08-28T09:58:10.435Z" },
178
+ { url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901, upload-time = "2026-08-28T09:58:12.033Z" },
179
+ { url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425, upload-time = "2026-08-28T09:58:13.614Z" },
180
+ { url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566, upload-time = "2026-08-28T09:58:15.199Z" },
181
+ { url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258, upload-time = "2026-08-28T09:58:16.813Z" },
182
+ { url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030, upload-time = "2026-08-28T09:58:18.288Z" },
183
+ { url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234, upload-time = "2026-08-28T09:58:19.929Z" },
184
+ { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" },
185
+ { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" },
186
+ { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" },
187
+ { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" },
188
+ { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" },
189
+ { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" },
190
+ { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" },
191
+ { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" },
192
+ { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" },
193
+ { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" },
194
+ { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" },
195
+ { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" },
196
+ { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" },
197
+ { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" },
198
+ { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" },
199
+ { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" },
200
+ { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" },
201
+ { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" },
202
+ { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" },
203
+ { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" },
204
+ { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" },
205
+ { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" },
206
+ { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" },
207
+ { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" },
208
+ { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" },
209
+ { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" },
210
+ { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" },
211
+ { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" },
212
+ { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" },
213
+ { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" },
214
+ { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" },
215
+ { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" },
216
+ { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" },
217
+ { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" },
218
+ { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" },
219
+ { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" },
220
+ { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" },
221
+ { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" },
222
+ { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" },
223
+ { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" },
224
+ { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" },
225
+ { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" },
226
+ { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" },
227
+ { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" },
228
+ { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" },
229
+ { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" },
230
+ { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" },
231
+ { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" },
232
+ { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" },
233
+ { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" },
234
+ { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" },
235
+ { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" },
236
+ { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" },
237
+ { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" },
238
+ { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" },
239
+ { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" },
240
+ { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" },
241
+ { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" },
242
+ { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" },
243
+ { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" },
244
+ { url = "https://files.pythonhosted.org/packages/af/1e/ecca01fce348f7e8afa9572441ff6f7d1cc70d21e4859f33944d10877e1e/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2", size = 2075342, upload-time = "2026-08-28T10:00:51.353Z" },
245
+ { url = "https://files.pythonhosted.org/packages/1f/4c/af80c7a8032dfc897040ad5cb772bebde529a381186499e6e29987f23f8c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c", size = 1907219, upload-time = "2026-08-28T10:00:53.438Z" },
246
+ { url = "https://files.pythonhosted.org/packages/be/3e/54d89e2b092e778716bf6153634ef479e955f48c261090be23aa1e0fb0b5/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47", size = 1953393, upload-time = "2026-08-28T10:00:55.58Z" },
247
+ { url = "https://files.pythonhosted.org/packages/ea/89/828ee90cda28ce17bdefaa3a6eaf74fe430e113295a10e6126beca559d6c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a", size = 2099024, upload-time = "2026-08-28T10:00:57.794Z" },
248
+ { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" },
249
+ { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" },
250
+ { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" },
251
+ { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" },
252
+ { url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333, upload-time = "2026-08-28T10:01:09.636Z" },
253
+ { url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713, upload-time = "2026-08-28T10:01:12.285Z" },
254
+ { url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926, upload-time = "2026-08-28T10:01:15.19Z" },
255
+ { url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303, upload-time = "2026-08-28T10:01:17.937Z" },
256
+ { url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128, upload-time = "2026-08-28T10:01:20.694Z" },
257
+ { url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560, upload-time = "2026-08-28T10:01:23.495Z" },
258
+ { url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531, upload-time = "2026-08-28T10:01:26.334Z" },
259
+ { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" },
260
+ ]
261
+
262
+ [[package]]
263
+ name = "pygments"
264
+ version = "2.21.0"
265
+ source = { registry = "https://pypi.org/simple" }
266
+ sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
267
+ wheels = [
268
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
269
+ ]
270
+
271
+ [[package]]
272
+ name = "pytest"
273
+ version = "9.1.1"
274
+ source = { registry = "https://pypi.org/simple" }
275
+ dependencies = [
276
+ { name = "colorama", marker = "sys_platform == 'win32'" },
277
+ { name = "iniconfig" },
278
+ { name = "packaging" },
279
+ { name = "pluggy" },
280
+ { name = "pygments" },
281
+ ]
282
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
283
+ wheels = [
284
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
285
+ ]
286
+
287
+ [[package]]
288
+ name = "ruff"
289
+ version = "0.16.8"
290
+ source = { registry = "https://pypi.org/simple" }
291
+ sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" }
292
+ wheels = [
293
+ { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" },
294
+ { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" },
295
+ { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" },
296
+ { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" },
297
+ { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" },
298
+ { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" },
299
+ { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" },
300
+ { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" },
301
+ { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" },
302
+ { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" },
303
+ { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" },
304
+ { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" },
305
+ { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" },
306
+ { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" },
307
+ { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" },
308
+ { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" },
309
+ { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" },
310
+ ]
311
+
312
+ [[package]]
313
+ name = "typing-extensions"
314
+ version = "4.16.0"
315
+ source = { registry = "https://pypi.org/simple" }
316
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
317
+ wheels = [
318
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
319
+ ]
320
+
321
+ [[package]]
322
+ name = "typing-inspection"
323
+ version = "0.4.4"
324
+ source = { registry = "https://pypi.org/simple" }
325
+ dependencies = [
326
+ { name = "typing-extensions" },
327
+ ]
328
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
329
+ wheels = [
330
+ { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
331
+ ]