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.
- flares_client-0.1.0/.gitignore +36 -0
- flares_client-0.1.0/PKG-INFO +42 -0
- flares_client-0.1.0/README.md +29 -0
- flares_client-0.1.0/pyproject.toml +30 -0
- flares_client-0.1.0/src/flares_client/__init__.py +47 -0
- flares_client-0.1.0/src/flares_client/_common.py +94 -0
- flares_client-0.1.0/src/flares_client/client.py +269 -0
- flares_client-0.1.0/src/flares_client/errors.py +29 -0
- flares_client-0.1.0/src/flares_client/models.py +140 -0
- flares_client-0.1.0/src/flares_client/py.typed +0 -0
- flares_client-0.1.0/tests/test_validation.py +55 -0
- flares_client-0.1.0/uv.lock +331 -0
|
@@ -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
|
+
]
|