astrocyte-ingestion-github 0.8.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.
- astrocyte_ingestion_github-0.8.0/.gitignore +53 -0
- astrocyte_ingestion_github-0.8.0/PKG-INFO +49 -0
- astrocyte_ingestion_github-0.8.0/README.md +36 -0
- astrocyte_ingestion_github-0.8.0/astrocyte_ingestion_github/__init__.py +5 -0
- astrocyte_ingestion_github-0.8.0/astrocyte_ingestion_github/source.py +278 -0
- astrocyte_ingestion_github-0.8.0/pyproject.toml +38 -0
- astrocyte_ingestion_github-0.8.0/tests/test_github_poll_source.py +75 -0
- astrocyte_ingestion_github-0.8.0/uv.lock +284 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Node (Starlight in docs/)
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
docs/.astro/
|
|
5
|
+
|
|
6
|
+
# Generated by docs/scripts/fetch-pypi-pins.mjs (recreated on every pnpm dev/build)
|
|
7
|
+
docs/src/data/pypi-install-pins.json
|
|
8
|
+
|
|
9
|
+
# Generated by docs/scripts/sync-docs.mjs (recreated on every pnpm dev/build)
|
|
10
|
+
docs/src/content/docs/introduction.md
|
|
11
|
+
docs/src/content/docs/design/
|
|
12
|
+
docs/src/content/docs/plugins/
|
|
13
|
+
docs/src/content/docs/end-user/
|
|
14
|
+
docs/src/content/docs/tutorials/
|
|
15
|
+
|
|
16
|
+
# Claude Code
|
|
17
|
+
.claude/
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
|
|
22
|
+
# Local reference material (not tracked)
|
|
23
|
+
references/
|
|
24
|
+
|
|
25
|
+
# nWave feature artifacts (per-feature discover/deliver, roadmaps, execution logs).
|
|
26
|
+
# Intended to be a separate private worktree or nested git repo; not part of public Astrocyte.
|
|
27
|
+
docs/feature/
|
|
28
|
+
|
|
29
|
+
# Python
|
|
30
|
+
__pycache__/
|
|
31
|
+
*.py[cod]
|
|
32
|
+
*$py.class
|
|
33
|
+
.venv/
|
|
34
|
+
venv/
|
|
35
|
+
.env
|
|
36
|
+
.env.*
|
|
37
|
+
!.env.example
|
|
38
|
+
*.egg-info/
|
|
39
|
+
.eggs/
|
|
40
|
+
dist/
|
|
41
|
+
build/
|
|
42
|
+
.pytest_cache/
|
|
43
|
+
.mypy_cache/
|
|
44
|
+
.ruff_cache/
|
|
45
|
+
.coverage
|
|
46
|
+
htmlcov/
|
|
47
|
+
benchmark-results/
|
|
48
|
+
|
|
49
|
+
# Rust
|
|
50
|
+
target/
|
|
51
|
+
|
|
52
|
+
# IDE (optional)
|
|
53
|
+
.idea/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astrocyte-ingestion-github
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: GitHub Issues API poll IngestSource adapter for Astrocyte
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: astrocyte<0.9,>=0.7.0
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# astrocyte-ingestion-github
|
|
15
|
+
|
|
16
|
+
**Poll** driver for Astrocyte `sources:` — ingests **GitHub repository issues** (not pull requests) via the [REST API](https://docs.github.com/en/rest/issues/issues#list-repository-issues).
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install astrocyte-ingestion-github
|
|
22
|
+
# or
|
|
23
|
+
pip install 'astrocyte[poll]'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Config (`astrocyte.yaml`)
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
sources:
|
|
30
|
+
gh_issues:
|
|
31
|
+
type: poll
|
|
32
|
+
driver: github
|
|
33
|
+
path: octocat/Hello-World # owner/repo
|
|
34
|
+
interval_seconds: 120 # >= 60 (GitHub API rate limits)
|
|
35
|
+
target_bank: engineering
|
|
36
|
+
auth:
|
|
37
|
+
token: ${GITHUB_TOKEN} # classic PAT or fine-grained token (issues read)
|
|
38
|
+
extraction_profile: builtin_text # optional
|
|
39
|
+
# Optional: GitHub Enterprise Server API root
|
|
40
|
+
# url: https://github.example.com/api/v3
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The adapter sets `Authorization: Bearer …` and uses `since` (max `updated_at` from the last response) to limit traffic. Each issue is retained as text `[GitHub #N] title` plus body; metadata includes `github.issue_id`, `number`, `html_url`, `updated_at`, `author`.
|
|
44
|
+
|
|
45
|
+
Principal for bank resolution: `sources.*.principal` if set; otherwise `github:<author_login>` from the issue.
|
|
46
|
+
|
|
47
|
+
## Entry point
|
|
48
|
+
|
|
49
|
+
Registers as **`github`** under **`astrocyte.ingest_poll_drivers`** (same discovery pattern as stream drivers).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# astrocyte-ingestion-github
|
|
2
|
+
|
|
3
|
+
**Poll** driver for Astrocyte `sources:` — ingests **GitHub repository issues** (not pull requests) via the [REST API](https://docs.github.com/en/rest/issues/issues#list-repository-issues).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install astrocyte-ingestion-github
|
|
9
|
+
# or
|
|
10
|
+
pip install 'astrocyte[poll]'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Config (`astrocyte.yaml`)
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
sources:
|
|
17
|
+
gh_issues:
|
|
18
|
+
type: poll
|
|
19
|
+
driver: github
|
|
20
|
+
path: octocat/Hello-World # owner/repo
|
|
21
|
+
interval_seconds: 120 # >= 60 (GitHub API rate limits)
|
|
22
|
+
target_bank: engineering
|
|
23
|
+
auth:
|
|
24
|
+
token: ${GITHUB_TOKEN} # classic PAT or fine-grained token (issues read)
|
|
25
|
+
extraction_profile: builtin_text # optional
|
|
26
|
+
# Optional: GitHub Enterprise Server API root
|
|
27
|
+
# url: https://github.example.com/api/v3
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The adapter sets `Authorization: Bearer …` and uses `since` (max `updated_at` from the last response) to limit traffic. Each issue is retained as text `[GitHub #N] title` plus body; metadata includes `github.issue_id`, `number`, `html_url`, `updated_at`, `author`.
|
|
31
|
+
|
|
32
|
+
Principal for bank resolution: `sources.*.principal` if set; otherwise `github:<author_login>` from the issue.
|
|
33
|
+
|
|
34
|
+
## Entry point
|
|
35
|
+
|
|
36
|
+
Registers as **`github`** under **`astrocyte.ingest_poll_drivers`** (same discovery pattern as stream drivers).
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""GitHub REST API poll :class:`~astrocyte.ingest.source.IngestSource` — repository issues."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import urlsplit
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from astrocyte.config import SourceConfig
|
|
13
|
+
from astrocyte.errors import IngestError
|
|
14
|
+
from astrocyte.ingest.bank_resolve import resolve_ingest_bank_id
|
|
15
|
+
from astrocyte.ingest.logutil import log_ingest_event
|
|
16
|
+
from astrocyte.ingest.webhook import RetainCallable
|
|
17
|
+
from astrocyte.types import AstrocyteContext, HealthStatus
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("astrocyte_ingestion_github")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _token(cfg: SourceConfig) -> str:
|
|
23
|
+
auth = cfg.auth or {}
|
|
24
|
+
raw = auth.get("token")
|
|
25
|
+
if raw is None or not str(raw).strip():
|
|
26
|
+
raise IngestError("GitHub poll requires auth.token")
|
|
27
|
+
return str(raw).strip()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _api_base(cfg: SourceConfig) -> str:
|
|
31
|
+
u = (cfg.url or "").strip()
|
|
32
|
+
if u:
|
|
33
|
+
return u.rstrip("/")
|
|
34
|
+
return "https://api.github.com"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _owner_repo(cfg: SourceConfig) -> tuple[str, str]:
|
|
38
|
+
p = (cfg.path or "").strip()
|
|
39
|
+
if not p or p.count("/") != 1:
|
|
40
|
+
raise IngestError("GitHub poll requires path: owner/repo")
|
|
41
|
+
owner, repo = p.split("/", 1)
|
|
42
|
+
if not owner.strip() or not repo.strip():
|
|
43
|
+
raise IngestError("GitHub poll path owner/repo must be non-empty")
|
|
44
|
+
return owner.strip(), repo.strip()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _int_header(raw: object) -> int | None:
|
|
48
|
+
"""Parse GitHub numeric response headers; invalid values become ``None``."""
|
|
49
|
+
try:
|
|
50
|
+
return int(str(raw).strip())
|
|
51
|
+
except ValueError:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class GithubPollIngestSource:
|
|
56
|
+
"""Poll ``GET /repos/{owner}/{repo}/issues`` and retain new/updated issues (not pull requests)."""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
source_id: str,
|
|
61
|
+
config: SourceConfig,
|
|
62
|
+
*,
|
|
63
|
+
retain: RetainCallable,
|
|
64
|
+
) -> None:
|
|
65
|
+
self._source_id = source_id
|
|
66
|
+
self._config = config
|
|
67
|
+
self._retain = retain
|
|
68
|
+
self._task: asyncio.Task[None] | None = None
|
|
69
|
+
self._stop = asyncio.Event()
|
|
70
|
+
self._client: httpx.AsyncClient | None = None
|
|
71
|
+
self._running = False
|
|
72
|
+
self._last_error: str | None = None
|
|
73
|
+
# GitHub issue id -> last ingested updated_at (ISO) to avoid duplicate retains
|
|
74
|
+
self._seen_updated: dict[int, str] = {}
|
|
75
|
+
self._since: str | None = None
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def source_id(self) -> str:
|
|
79
|
+
return self._source_id
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def source_type(self) -> str:
|
|
83
|
+
return "poll"
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def config(self) -> SourceConfig:
|
|
87
|
+
return self._config
|
|
88
|
+
|
|
89
|
+
def _interval_s(self) -> float:
|
|
90
|
+
n = self._config.interval_seconds
|
|
91
|
+
if n is None or int(n) < 60:
|
|
92
|
+
raise IngestError("poll requires interval_seconds >= 60")
|
|
93
|
+
return float(int(n))
|
|
94
|
+
|
|
95
|
+
async def start(self) -> None:
|
|
96
|
+
if self._task is not None:
|
|
97
|
+
return
|
|
98
|
+
self._stop.clear()
|
|
99
|
+
self._last_error = None
|
|
100
|
+
self._running = True
|
|
101
|
+
base = _api_base(self._config)
|
|
102
|
+
host = urlsplit(base).hostname or ""
|
|
103
|
+
self._client = httpx.AsyncClient(
|
|
104
|
+
base_url=base,
|
|
105
|
+
headers={
|
|
106
|
+
"Accept": "application/vnd.github+json",
|
|
107
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
108
|
+
"Authorization": f"Bearer {_token(self._config)}",
|
|
109
|
+
"User-Agent": "astrocyte-ingestion-github",
|
|
110
|
+
},
|
|
111
|
+
timeout=60.0,
|
|
112
|
+
)
|
|
113
|
+
# Enterprise often uses self-signed or custom CA; keep verify=True by default
|
|
114
|
+
if "github.com" not in host and host:
|
|
115
|
+
logger.info("github poll using API base %s", base)
|
|
116
|
+
self._task = asyncio.create_task(self._run_loop(), name=f"astrocyte-github-poll-{self._source_id}")
|
|
117
|
+
|
|
118
|
+
async def stop(self) -> None:
|
|
119
|
+
self._running = False
|
|
120
|
+
self._stop.set()
|
|
121
|
+
if self._task:
|
|
122
|
+
self._task.cancel()
|
|
123
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
124
|
+
await self._task
|
|
125
|
+
self._task = None
|
|
126
|
+
if self._client is not None:
|
|
127
|
+
await self._client.aclose()
|
|
128
|
+
self._client = None
|
|
129
|
+
|
|
130
|
+
async def health_check(self) -> HealthStatus:
|
|
131
|
+
if not self._running or self._task is None:
|
|
132
|
+
return HealthStatus(healthy=False, message="github poll source stopped")
|
|
133
|
+
if self._last_error:
|
|
134
|
+
return HealthStatus(healthy=False, message=self._last_error)
|
|
135
|
+
return HealthStatus(healthy=True, message="github poll loop running")
|
|
136
|
+
|
|
137
|
+
async def _run_loop(self) -> None:
|
|
138
|
+
assert self._client is not None
|
|
139
|
+
interval = self._interval_s()
|
|
140
|
+
while not self._stop.is_set():
|
|
141
|
+
try:
|
|
142
|
+
await self._poll_once()
|
|
143
|
+
except asyncio.CancelledError:
|
|
144
|
+
raise
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self._last_error = str(e)
|
|
147
|
+
log_ingest_event(
|
|
148
|
+
logger,
|
|
149
|
+
"github_poll_cycle_failed",
|
|
150
|
+
source_id=self._source_id,
|
|
151
|
+
error=str(e),
|
|
152
|
+
)
|
|
153
|
+
logger.exception("github poll failed for %s", self._source_id)
|
|
154
|
+
# interruptible sleep
|
|
155
|
+
try:
|
|
156
|
+
await asyncio.wait_for(self._stop.wait(), timeout=interval)
|
|
157
|
+
break
|
|
158
|
+
except asyncio.TimeoutError:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
async def _poll_once(self) -> None:
|
|
162
|
+
assert self._client is not None
|
|
163
|
+
owner, repo = _owner_repo(self._config)
|
|
164
|
+
params: dict[str, Any] = {
|
|
165
|
+
"state": "all",
|
|
166
|
+
"per_page": 50,
|
|
167
|
+
"sort": "updated",
|
|
168
|
+
"direction": "desc",
|
|
169
|
+
}
|
|
170
|
+
if self._since:
|
|
171
|
+
params["since"] = self._since
|
|
172
|
+
|
|
173
|
+
r = await self._client.get(f"/repos/{owner}/{repo}/issues", params=params)
|
|
174
|
+
rem_raw = r.headers.get("x-ratelimit-remaining") or r.headers.get("X-RateLimit-Remaining")
|
|
175
|
+
if rem_raw is not None:
|
|
176
|
+
rem = _int_header(rem_raw)
|
|
177
|
+
if rem is not None and rem < 20:
|
|
178
|
+
log_ingest_event(
|
|
179
|
+
logger,
|
|
180
|
+
"github_poll_rate_limit_low",
|
|
181
|
+
source_id=self._source_id,
|
|
182
|
+
remaining=rem,
|
|
183
|
+
reset=r.headers.get("x-ratelimit-reset") or r.headers.get("X-RateLimit-Reset"),
|
|
184
|
+
)
|
|
185
|
+
try:
|
|
186
|
+
r.raise_for_status()
|
|
187
|
+
except httpx.HTTPStatusError as e:
|
|
188
|
+
log_ingest_event(
|
|
189
|
+
logger,
|
|
190
|
+
"github_poll_http_error",
|
|
191
|
+
source_id=self._source_id,
|
|
192
|
+
status_code=e.response.status_code,
|
|
193
|
+
url=str(e.request.url),
|
|
194
|
+
)
|
|
195
|
+
raise
|
|
196
|
+
issues = r.json()
|
|
197
|
+
if not isinstance(issues, list):
|
|
198
|
+
raise IngestError("GitHub issues response must be a JSON array")
|
|
199
|
+
|
|
200
|
+
cursor_max: str | None = None
|
|
201
|
+
for issue in issues:
|
|
202
|
+
if not isinstance(issue, dict):
|
|
203
|
+
continue
|
|
204
|
+
uat = issue.get("updated_at")
|
|
205
|
+
if isinstance(uat, str):
|
|
206
|
+
if cursor_max is None or uat > cursor_max:
|
|
207
|
+
cursor_max = uat
|
|
208
|
+
|
|
209
|
+
for issue in issues:
|
|
210
|
+
if not isinstance(issue, dict):
|
|
211
|
+
continue
|
|
212
|
+
if issue.get("pull_request"):
|
|
213
|
+
continue
|
|
214
|
+
iid = issue.get("id")
|
|
215
|
+
upd = issue.get("updated_at")
|
|
216
|
+
if not isinstance(iid, int) or not isinstance(upd, str):
|
|
217
|
+
continue
|
|
218
|
+
if self._seen_updated.get(iid) == upd:
|
|
219
|
+
continue
|
|
220
|
+
title = issue.get("title")
|
|
221
|
+
body = issue.get("body")
|
|
222
|
+
num = issue.get("number")
|
|
223
|
+
html_url = issue.get("html_url")
|
|
224
|
+
t = title if isinstance(title, str) else ""
|
|
225
|
+
b = body if isinstance(body, str) else ""
|
|
226
|
+
n = num if isinstance(num, int) else 0
|
|
227
|
+
u = html_url if isinstance(html_url, str) else ""
|
|
228
|
+
text = f"[GitHub #{n}] {t}\n\n{b}".strip()
|
|
229
|
+
if not text:
|
|
230
|
+
text = f"[GitHub #{n}] (empty)"
|
|
231
|
+
|
|
232
|
+
user = issue.get("user")
|
|
233
|
+
login = None
|
|
234
|
+
if isinstance(user, dict):
|
|
235
|
+
lg = user.get("login")
|
|
236
|
+
if isinstance(lg, str):
|
|
237
|
+
login = lg
|
|
238
|
+
|
|
239
|
+
eff_principal = self._config.principal
|
|
240
|
+
if isinstance(eff_principal, str) and eff_principal.strip():
|
|
241
|
+
p = eff_principal.strip()
|
|
242
|
+
elif login:
|
|
243
|
+
p = f"github:{login}"
|
|
244
|
+
else:
|
|
245
|
+
p = None
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
bank_id = resolve_ingest_bank_id(self._config, principal=p)
|
|
249
|
+
except IngestError as e:
|
|
250
|
+
logger.warning("github poll %s skip issue %s: %s", self._source_id, iid, e)
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
profile = self._config.extraction_profile
|
|
254
|
+
ctx = AstrocyteContext(principal=p) if p else None
|
|
255
|
+
metadata = {
|
|
256
|
+
"github": {
|
|
257
|
+
"issue_id": iid,
|
|
258
|
+
"number": n,
|
|
259
|
+
"html_url": u,
|
|
260
|
+
"updated_at": upd,
|
|
261
|
+
"author": login,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await self._retain(
|
|
266
|
+
text,
|
|
267
|
+
bank_id,
|
|
268
|
+
metadata=metadata,
|
|
269
|
+
content_type="text",
|
|
270
|
+
extraction_profile=profile,
|
|
271
|
+
source=self._source_id,
|
|
272
|
+
context=ctx,
|
|
273
|
+
)
|
|
274
|
+
self._seen_updated[iid] = upd
|
|
275
|
+
|
|
276
|
+
if cursor_max:
|
|
277
|
+
self._since = cursor_max
|
|
278
|
+
self._last_error = None
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "astrocyte-ingestion-github"
|
|
7
|
+
version = "0.8.0"
|
|
8
|
+
description = "GitHub Issues API poll IngestSource adapter for Astrocyte"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"astrocyte>=0.7.0,<0.9",
|
|
14
|
+
"httpx>=0.27",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
|
|
19
|
+
|
|
20
|
+
[project.entry-points."astrocyte.ingest_poll_drivers"]
|
|
21
|
+
github = "astrocyte_ingestion_github.source:GithubPollIngestSource"
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["astrocyte_ingestion_github"]
|
|
25
|
+
|
|
26
|
+
[tool.uv.sources]
|
|
27
|
+
astrocyte = { path = "../../astrocyte-py", editable = true }
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
asyncio_mode = "auto"
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 120
|
|
35
|
+
target-version = "py311"
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint]
|
|
38
|
+
select = ["E", "W", "F", "I"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Unit tests for GithubPollIngestSource."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import pytest
|
|
7
|
+
from astrocyte.config import SourceConfig
|
|
8
|
+
|
|
9
|
+
from astrocyte_ingestion_github import GithubPollIngestSource
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.asyncio
|
|
13
|
+
async def test_poll_once_skips_pull_requests_and_recalls_retain() -> None:
|
|
14
|
+
retained: list[tuple[str, str, dict[str, object]]] = []
|
|
15
|
+
|
|
16
|
+
async def retain(text: str, bank_id: str, **kwargs: object) -> object:
|
|
17
|
+
from astrocyte.types import RetainResult
|
|
18
|
+
|
|
19
|
+
meta = kwargs.get("metadata")
|
|
20
|
+
retained.append((text, bank_id, meta if isinstance(meta, dict) else {}))
|
|
21
|
+
return RetainResult(stored=True, memory_id="m1")
|
|
22
|
+
|
|
23
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
24
|
+
assert request.method == "GET"
|
|
25
|
+
assert "/repos/o/r/issues" in str(request.url)
|
|
26
|
+
payload = [
|
|
27
|
+
{
|
|
28
|
+
"id": 1001,
|
|
29
|
+
"updated_at": "2024-01-02T00:00:00Z",
|
|
30
|
+
"title": "Hello",
|
|
31
|
+
"body": "World",
|
|
32
|
+
"number": 10,
|
|
33
|
+
"html_url": "https://github.com/o/r/issues/10",
|
|
34
|
+
"user": {"login": "bob"},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": 1002,
|
|
38
|
+
"updated_at": "2024-01-01T00:00:00Z",
|
|
39
|
+
"title": "PR",
|
|
40
|
+
"pull_request": {
|
|
41
|
+
"url": "https://api.github.com/repos/o/r/pulls/1",
|
|
42
|
+
"html_url": "https://github.com/o/r/pull/1",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
return httpx.Response(200, json=payload)
|
|
47
|
+
|
|
48
|
+
cfg = SourceConfig(
|
|
49
|
+
type="poll",
|
|
50
|
+
driver="github",
|
|
51
|
+
path="o/r",
|
|
52
|
+
interval_seconds=60,
|
|
53
|
+
target_bank="bank-a",
|
|
54
|
+
auth={"token": "ghp_test_token"},
|
|
55
|
+
)
|
|
56
|
+
src = GithubPollIngestSource("gh", cfg, retain=retain)
|
|
57
|
+
src._client = httpx.AsyncClient(
|
|
58
|
+
transport=httpx.MockTransport(handler),
|
|
59
|
+
base_url="https://api.github.com",
|
|
60
|
+
headers={
|
|
61
|
+
"Accept": "application/vnd.github+json",
|
|
62
|
+
"Authorization": "Bearer ghp_test_token",
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
try:
|
|
66
|
+
await src._poll_once()
|
|
67
|
+
finally:
|
|
68
|
+
await src._client.aclose()
|
|
69
|
+
src._client = None
|
|
70
|
+
|
|
71
|
+
assert len(retained) == 1
|
|
72
|
+
assert retained[0][1] == "bank-a"
|
|
73
|
+
assert "[GitHub #10]" in retained[0][0]
|
|
74
|
+
assert "Hello" in retained[0][0]
|
|
75
|
+
assert retained[0][2].get("github", {}).get("number") == 10
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "anyio"
|
|
7
|
+
version = "4.13.0"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "idna" },
|
|
11
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
12
|
+
]
|
|
13
|
+
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
|
14
|
+
wheels = [
|
|
15
|
+
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[[package]]
|
|
19
|
+
name = "astrocyte"
|
|
20
|
+
source = { editable = "../../astrocyte-py" }
|
|
21
|
+
dependencies = [
|
|
22
|
+
{ name = "httpx" },
|
|
23
|
+
{ name = "pyyaml" },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[package.metadata]
|
|
27
|
+
requires-dist = [
|
|
28
|
+
{ name = "astrocyte-ingestion-github", marker = "extra == 'all'", editable = "." },
|
|
29
|
+
{ name = "astrocyte-ingestion-github", marker = "extra == 'dev'", editable = "." },
|
|
30
|
+
{ name = "astrocyte-ingestion-github", marker = "extra == 'poll'", editable = "." },
|
|
31
|
+
{ name = "astrocyte-ingestion-kafka", marker = "extra == 'all'", editable = "../astrocyte-ingestion-kafka" },
|
|
32
|
+
{ name = "astrocyte-ingestion-kafka", marker = "extra == 'dev'", editable = "../astrocyte-ingestion-kafka" },
|
|
33
|
+
{ name = "astrocyte-ingestion-kafka", marker = "extra == 'stream'", editable = "../astrocyte-ingestion-kafka" },
|
|
34
|
+
{ name = "astrocyte-ingestion-redis", marker = "extra == 'all'", editable = "../astrocyte-ingestion-redis" },
|
|
35
|
+
{ name = "astrocyte-ingestion-redis", marker = "extra == 'dev'", editable = "../astrocyte-ingestion-redis" },
|
|
36
|
+
{ name = "astrocyte-ingestion-redis", marker = "extra == 'stream'", editable = "../astrocyte-ingestion-redis" },
|
|
37
|
+
{ name = "deepeval", marker = "extra == 'all'", specifier = ">=2.0" },
|
|
38
|
+
{ name = "deepeval", marker = "extra == 'dev'", specifier = ">=2.0" },
|
|
39
|
+
{ name = "deepeval", marker = "extra == 'eval'", specifier = ">=2.0" },
|
|
40
|
+
{ name = "fastapi", marker = "extra == 'all'", specifier = ">=0.115" },
|
|
41
|
+
{ name = "fastapi", marker = "extra == 'dev'", specifier = ">=0.115" },
|
|
42
|
+
{ name = "fastapi", marker = "extra == 'gateway'", specifier = ">=0.115" },
|
|
43
|
+
{ name = "fastmcp", marker = "extra == 'all'", specifier = ">=3.0" },
|
|
44
|
+
{ name = "fastmcp", marker = "extra == 'dev'", specifier = ">=3.0" },
|
|
45
|
+
{ name = "fastmcp", marker = "extra == 'mcp'", specifier = ">=3.0" },
|
|
46
|
+
{ name = "httpx", specifier = ">=0.27" },
|
|
47
|
+
{ name = "openai", marker = "extra == 'all'", specifier = ">=1.0" },
|
|
48
|
+
{ name = "openai", marker = "extra == 'openai'", specifier = ">=1.0" },
|
|
49
|
+
{ name = "opentelemetry-api", marker = "extra == 'all'", specifier = ">=1.20" },
|
|
50
|
+
{ name = "opentelemetry-api", marker = "extra == 'otel'", specifier = ">=1.20" },
|
|
51
|
+
{ name = "opentelemetry-sdk", marker = "extra == 'all'", specifier = ">=1.20" },
|
|
52
|
+
{ name = "opentelemetry-sdk", marker = "extra == 'otel'", specifier = ">=1.20" },
|
|
53
|
+
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0" },
|
|
54
|
+
{ name = "prometheus-client", marker = "extra == 'all'", specifier = ">=0.20" },
|
|
55
|
+
{ name = "prometheus-client", marker = "extra == 'prometheus'", specifier = ">=0.20" },
|
|
56
|
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" },
|
|
57
|
+
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" },
|
|
58
|
+
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0" },
|
|
59
|
+
{ name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.2" },
|
|
60
|
+
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.5" },
|
|
61
|
+
{ name = "pyyaml", specifier = ">=6.0" },
|
|
62
|
+
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.4" },
|
|
63
|
+
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.30" },
|
|
64
|
+
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'dev'", specifier = ">=0.30" },
|
|
65
|
+
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'gateway'", specifier = ">=0.30" },
|
|
66
|
+
]
|
|
67
|
+
provides-extras = ["all", "dev", "eval", "gateway", "mcp", "openai", "otel", "poll", "prometheus", "stream"]
|
|
68
|
+
|
|
69
|
+
[[package]]
|
|
70
|
+
name = "astrocyte-ingestion-github"
|
|
71
|
+
version = "0.1.0"
|
|
72
|
+
source = { editable = "." }
|
|
73
|
+
dependencies = [
|
|
74
|
+
{ name = "astrocyte" },
|
|
75
|
+
{ name = "httpx" },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[package.optional-dependencies]
|
|
79
|
+
dev = [
|
|
80
|
+
{ name = "pytest" },
|
|
81
|
+
{ name = "pytest-asyncio" },
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
[package.metadata]
|
|
85
|
+
requires-dist = [
|
|
86
|
+
{ name = "astrocyte", editable = "../../astrocyte-py" },
|
|
87
|
+
{ name = "httpx", specifier = ">=0.27" },
|
|
88
|
+
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" },
|
|
89
|
+
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" },
|
|
90
|
+
]
|
|
91
|
+
provides-extras = ["dev"]
|
|
92
|
+
|
|
93
|
+
[[package]]
|
|
94
|
+
name = "certifi"
|
|
95
|
+
version = "2026.2.25"
|
|
96
|
+
source = { registry = "https://pypi.org/simple" }
|
|
97
|
+
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
|
98
|
+
wheels = [
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[[package]]
|
|
103
|
+
name = "colorama"
|
|
104
|
+
version = "0.4.6"
|
|
105
|
+
source = { registry = "https://pypi.org/simple" }
|
|
106
|
+
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" }
|
|
107
|
+
wheels = [
|
|
108
|
+
{ 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" },
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "h11"
|
|
113
|
+
version = "0.16.0"
|
|
114
|
+
source = { registry = "https://pypi.org/simple" }
|
|
115
|
+
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" }
|
|
116
|
+
wheels = [
|
|
117
|
+
{ 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" },
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[[package]]
|
|
121
|
+
name = "httpcore"
|
|
122
|
+
version = "1.0.9"
|
|
123
|
+
source = { registry = "https://pypi.org/simple" }
|
|
124
|
+
dependencies = [
|
|
125
|
+
{ name = "certifi" },
|
|
126
|
+
{ name = "h11" },
|
|
127
|
+
]
|
|
128
|
+
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" }
|
|
129
|
+
wheels = [
|
|
130
|
+
{ 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" },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[[package]]
|
|
134
|
+
name = "httpx"
|
|
135
|
+
version = "0.28.1"
|
|
136
|
+
source = { registry = "https://pypi.org/simple" }
|
|
137
|
+
dependencies = [
|
|
138
|
+
{ name = "anyio" },
|
|
139
|
+
{ name = "certifi" },
|
|
140
|
+
{ name = "httpcore" },
|
|
141
|
+
{ name = "idna" },
|
|
142
|
+
]
|
|
143
|
+
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" }
|
|
144
|
+
wheels = [
|
|
145
|
+
{ 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" },
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
[[package]]
|
|
149
|
+
name = "idna"
|
|
150
|
+
version = "3.11"
|
|
151
|
+
source = { registry = "https://pypi.org/simple" }
|
|
152
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
|
153
|
+
wheels = [
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
[[package]]
|
|
158
|
+
name = "iniconfig"
|
|
159
|
+
version = "2.3.0"
|
|
160
|
+
source = { registry = "https://pypi.org/simple" }
|
|
161
|
+
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" }
|
|
162
|
+
wheels = [
|
|
163
|
+
{ 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" },
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
[[package]]
|
|
167
|
+
name = "packaging"
|
|
168
|
+
version = "26.0"
|
|
169
|
+
source = { registry = "https://pypi.org/simple" }
|
|
170
|
+
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
|
|
171
|
+
wheels = [
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
[[package]]
|
|
176
|
+
name = "pluggy"
|
|
177
|
+
version = "1.6.0"
|
|
178
|
+
source = { registry = "https://pypi.org/simple" }
|
|
179
|
+
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" }
|
|
180
|
+
wheels = [
|
|
181
|
+
{ 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" },
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
[[package]]
|
|
185
|
+
name = "pygments"
|
|
186
|
+
version = "2.20.0"
|
|
187
|
+
source = { registry = "https://pypi.org/simple" }
|
|
188
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
|
189
|
+
wheels = [
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
[[package]]
|
|
194
|
+
name = "pytest"
|
|
195
|
+
version = "9.0.3"
|
|
196
|
+
source = { registry = "https://pypi.org/simple" }
|
|
197
|
+
dependencies = [
|
|
198
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
199
|
+
{ name = "iniconfig" },
|
|
200
|
+
{ name = "packaging" },
|
|
201
|
+
{ name = "pluggy" },
|
|
202
|
+
{ name = "pygments" },
|
|
203
|
+
]
|
|
204
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
|
205
|
+
wheels = [
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
[[package]]
|
|
210
|
+
name = "pytest-asyncio"
|
|
211
|
+
version = "1.3.0"
|
|
212
|
+
source = { registry = "https://pypi.org/simple" }
|
|
213
|
+
dependencies = [
|
|
214
|
+
{ name = "pytest" },
|
|
215
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
216
|
+
]
|
|
217
|
+
sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
|
|
218
|
+
wheels = [
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
[[package]]
|
|
223
|
+
name = "pyyaml"
|
|
224
|
+
version = "6.0.3"
|
|
225
|
+
source = { registry = "https://pypi.org/simple" }
|
|
226
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
|
227
|
+
wheels = [
|
|
228
|
+
{ url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
|
|
229
|
+
{ url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
|
|
230
|
+
{ url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
|
|
231
|
+
{ url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
|
|
236
|
+
{ url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
|
253
|
+
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
|
254
|
+
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
|
255
|
+
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
|
256
|
+
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
|
257
|
+
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
|
258
|
+
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
|
261
|
+
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
|
263
|
+
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
|
264
|
+
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
|
266
|
+
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
|
267
|
+
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
|
268
|
+
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
|
269
|
+
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
|
270
|
+
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
|
271
|
+
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
|
272
|
+
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
|
273
|
+
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
|
274
|
+
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
[[package]]
|
|
278
|
+
name = "typing-extensions"
|
|
279
|
+
version = "4.15.0"
|
|
280
|
+
source = { registry = "https://pypi.org/simple" }
|
|
281
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
282
|
+
wheels = [
|
|
283
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
284
|
+
]
|