Compare commits
2 Commits
c88b9a770b
...
d80db75d4e
| Author | SHA1 | Date | |
|---|---|---|---|
| d80db75d4e | |||
| 46915964e1 |
76
README.md
76
README.md
@ -1,3 +1,73 @@
|
|||||||
# University-Playwright-Codegen-Agent
|
# University Playwright Codegen Agent
|
||||||
|
|
||||||
这是一个自动化生成代码的agent,给定一个海外大学官网的网址,即可生成python脚本,爬取这个大学各级学院下的所有硕士项目的网址和硕士项目中各导师个人信息的网址。
|
构建于 [Agno](https://docs.agno.com/) 的自动化代码生成代理:输入海外大学官网的根地址,即可生成一份使用 **Playwright** 的 Python 脚本,脚本会抓取各学院/研究生院下的硕士项目网址以及项目中列出的导师(Supervisor/Faculty)个人信息页面。本项目使用 `uv` 进行依赖管理,`ruff` 做静态检查,`ty` 负责类型检查,并提供了一个基于 Typer 的 CLI。
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ **Agno Agent**:利用 `output_schema` 强制结构化输出,里程碑式地生成 `ScriptPlan` 并将其渲染为可执行脚本。
|
||||||
|
- ✅ **Playwright sampling**:计划生成前会用 Playwright 对站点进行轻量抓取,帮助 Agent 找到关键词与导航策略。
|
||||||
|
- ✅ **Deterministic script template**:脚本模板包含 BFS 爬取、关键词过滤、JSON 输出等逻辑,确保满足“硕士项目 + 导师”需求。
|
||||||
|
- ✅ **uv + ruff + ty workflow**:开箱即用的现代 Python 工具链。
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
1. **创建虚拟环境并安装依赖**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv venv --python 3.12
|
||||||
|
uv pip install -r pyproject.toml
|
||||||
|
playwright install # 安装浏览器内核
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **配置大模型 API key**
|
||||||
|
|
||||||
|
- OpenAI: `export OPENAI_API_KEY=...`
|
||||||
|
- Anthropic: `export ANTHROPIC_API_KEY=...`
|
||||||
|
- 可通过环境变量 `CODEGEN_MODEL_PROVIDER` 在 `openai` 与 `anthropic` 之间切换。
|
||||||
|
|
||||||
|
3. **运行 CLI 生成脚本**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run university-agent generate \
|
||||||
|
"https://www.example.edu" \
|
||||||
|
--campus "Example Campus" \
|
||||||
|
--language "English" \
|
||||||
|
--max-depth 2 \
|
||||||
|
--max-pages 60
|
||||||
|
```
|
||||||
|
|
||||||
|
运行完成后会在 `artifacts/` 下看到生成的 Playwright 脚本,并在终端展示自动规划的关键词与验证步骤。
|
||||||
|
|
||||||
|
4. **执行 Ruff & Ty 检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run ruff check
|
||||||
|
uvx ty check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── README.md
|
||||||
|
├── pyproject.toml
|
||||||
|
├── src/university_agent
|
||||||
|
│ ├── agent.py # Agno Agent 配置
|
||||||
|
│ ├── cli.py # Typer CLI
|
||||||
|
│ ├── config.py # pydantic Settings
|
||||||
|
│ ├── generator.py # Orchestration 引擎
|
||||||
|
│ ├── models.py # 数据模型(请求/计划/结果)
|
||||||
|
│ ├── renderer.py # ScriptPlan -> Playwright script
|
||||||
|
│ ├── sampler.py # Playwright 采样
|
||||||
|
│ ├── templates/
|
||||||
|
│ │ └── playwright_script.py.jinja
|
||||||
|
│ └── writer.py # 将脚本写入 artifacts/
|
||||||
|
└── 任务1.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- `university-agent generate --help` 查看所有 CLI 选项,可选择跳过采样或导出规划 JSON。
|
||||||
|
- 如果 Agno Agent 需使用其他工具,可在 `agent.py` 中自行扩展自定义 `tool`。
|
||||||
|
- Playwright 采样在某些环境中需要额外的浏览器依赖,请根据官方提示执行 `playwright install`。
|
||||||
|
|
||||||
|
Happy building! 🎓🤖
|
||||||
|
|||||||
234
artifacts/stanford_playwright_scraper.py
Normal file
234
artifacts/stanford_playwright_scraper.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Auto-generated by the Agno codegen agent.
|
||||||
|
Target university: Stanford (https://www.stanford.edu)
|
||||||
|
Requested caps: depth=2, pages=20
|
||||||
|
|
||||||
|
Plan description: Async Playwright scraper targeting Stanford University to collect (1) master's program landing pages and (2) faculty/supervisor profiles linked from those programs. Respects depth=2, pages=20 caps. Starts from the main site, navigates through Academics and school/department portals to locate graduate program listings and associated faculty directories.
|
||||||
|
Navigation strategy: 1. Seed from https://www.stanford.edu and follow the 'Academics' top-nav link to reach school/department listings. 2. On school pages, look for anchors containing master_program_keywords (href fragments like /programs/, /graduate/, /masters/) and queue them. 3. On each master's program page, extract metadata and scan for faculty_keywords in link text or href (e.g., /people/, /faculty/, /directory/). 4. Follow qualifying faculty links up to the depth cap and record each profile. 5. Use URL deduplication (set of visited URLs) and respect the 20-page cap by counting unique pages fetched. 6. Prioritize links via keyword score so high-relevance pages are visited first if cap is approached.
|
||||||
|
Verification checklist:
|
||||||
|
- Assert each JSONL row contains a non-empty 'url' and 'title' field.
|
||||||
|
- Confirm at least one record has entity_type='master_program' and one has entity_type='faculty'.
|
||||||
|
- Validate that no URL appears more than once in the output file.
|
||||||
|
- Check that total scraped pages does not exceed the requested cap of 20.
|
||||||
|
Playwright snapshot used to guide this plan:
|
||||||
|
1. Stanford University (https://www.stanford.edu)
|
||||||
|
Snippet: Skip to content Stanford University Information for: Students Faculty & Staff Families Visitors Alumni Search Academics Research Health Care Campus Life Athletics Admission About News Events Stanford Explore Stanford Main Content A Mission Defined by Possibility At Stanford,
|
||||||
|
Anchors: Skip to content -> https://www.stanford.edu/#main-content, Stanford University -> https://www.stanford.edu/, Students -> https://www.stanford.edu/student-gateway/, Faculty & Staff -> https://www.stanford.edu/faculty-staff-gateway/, Families -> http://parents.stanford.edu/, Visitors -> https://visit.stanford.edu/
|
||||||
|
2. Stanford University (https://www.stanford.edu/)
|
||||||
|
Snippet: Skip to content Stanford University Information for: Students Faculty & Staff Families Visitors Alumni Search Academics Research Health Care Campus Life Athletics Admission About News Events Stanford Explore Stanford Main Content A Mission Defined by Possibility At Stanford,
|
||||||
|
Anchors: Skip to content -> https://www.stanford.edu/#main-content, Stanford University -> https://www.stanford.edu/, Students -> https://www.stanford.edu/student-gateway/, Faculty & Staff -> https://www.stanford.edu/faculty-staff-gateway/, Families -> http://parents.stanford.edu/, Visitors -> https://visit.stanford.edu/
|
||||||
|
3. Gateway for Students – Stanford University (https://www.stanford.edu/student-gateway/)
|
||||||
|
Snippet: Skip to content Stanford University Information for: Students Faculty & Staff Families Visitors Alumni Search Academics Research Health Care Campus Life Athletics Admission About News Events Main Content Gateway for Students Gateway for Students Resources, offices and services t
|
||||||
|
Anchors: Skip to content -> https://www.stanford.edu/student-gateway/#main-content, Stanford University -> https://www.stanford.edu/, Students -> https://www.stanford.edu/student-gateway/, Faculty & Staff -> https://www.stanford.edu/faculty-staff-gateway/, Families -> http://parents.stanford.edu/, Visitors -> https://visit.stanford.edu/
|
||||||
|
4. Gateway for Faculty & Staff – Stanford University (https://www.stanford.edu/faculty-staff-gateway/)
|
||||||
|
Snippet: Skip to content Stanford University Information for: Students Faculty & Staff Families Visitors Alumni Search Academics Research Health Care Campus Life Athletics Admission About News Events Main Content Gateway for Faculty & Staff Gateway for Faculty & Staff Resources, offices,
|
||||||
|
Anchors: Skip to content -> https://www.stanford.edu/faculty-staff-gateway/#main-content, Stanford University -> https://www.stanford.edu/, Students -> https://www.stanford.edu/student-gateway/, Faculty & Staff -> https://www.stanford.edu/faculty-staff-gateway/, Families -> http://parents.stanford.edu/, Visitors -> https://visit.stanford.edu/
|
||||||
|
Snapshot truncated.
|
||||||
|
|
||||||
|
Generated at: 2025-12-09T04:03:37.802236+00:00
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Deque, Iterable, List, Set, Tuple
|
||||||
|
from urllib.parse import urljoin, urldefrag
|
||||||
|
|
||||||
|
from playwright.async_api import async_playwright, Page
|
||||||
|
|
||||||
|
PROGRAM_KEYWORDS = ['master', 'graduate program', 'MS degree', 'MA degree', 'graduate studies', 'postgraduate']
|
||||||
|
FACULTY_KEYWORDS = ['faculty', 'professor', 'advisor', 'researcher', 'people', 'directory']
|
||||||
|
EXCLUSION_KEYWORDS = ['admission', 'apply', 'tuition', 'financial aid', 'login', 'news', 'events', 'alumni', 'athletics', 'giving', 'donate', 'careers', 'jobs']
|
||||||
|
METADATA_FIELDS = ['url', 'title', 'entity_type', 'department', 'school', 'description', 'contact_email', 'scraped_at']
|
||||||
|
EXTRA_NOTES = ["Stanford's seven schools each host their own subdomains (e.g., engineering.stanford.edu, law.stanford.edu); the script should allow cross-subdomain crawling within *.stanford.edu while still counting against the page cap.", "Many program pages load additional content via JavaScript; use page.wait_for_load_state('networkidle') or explicit waits before extracting links.", 'Faculty directories may paginate or use infinite scroll; handle at least the first visible batch given the tight page cap.', 'Respect polite crawling: insert a 1-2 second delay between requests to avoid rate-limiting.']
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_url(base: str, href: str) -> str:
|
||||||
|
absolute = urljoin(base, href)
|
||||||
|
cleaned, _ = urldefrag(absolute)
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
|
def matches_any(text: str, keywords: Iterable[str]) -> bool:
|
||||||
|
lowered = text.lower()
|
||||||
|
return any(keyword.lower() in lowered for keyword in keywords)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScrapedLink:
|
||||||
|
url: str
|
||||||
|
text: str
|
||||||
|
source_url: str
|
||||||
|
bucket: str # either "program" or "faculty"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScrapeSettings:
|
||||||
|
root_url: str
|
||||||
|
max_depth: int
|
||||||
|
max_pages: int
|
||||||
|
headless: bool
|
||||||
|
output: Path
|
||||||
|
|
||||||
|
|
||||||
|
async def extract_links(page: Page) -> List[Tuple[str, str]]:
|
||||||
|
anchors: Iterable[dict] = await page.eval_on_selector_all(
|
||||||
|
"a",
|
||||||
|
"""elements => elements
|
||||||
|
.map(el => ({text: (el.textContent || '').trim(), href: el.href}))
|
||||||
|
.filter(item => item.text && item.href)""",
|
||||||
|
)
|
||||||
|
return [(item["href"], item["text"]) for item in anchors]
|
||||||
|
|
||||||
|
|
||||||
|
async def crawl(settings: ScrapeSettings, browser_name: str) -> List[ScrapedLink]:
|
||||||
|
async with async_playwright() as p:
|
||||||
|
browser_launcher = getattr(p, browser_name)
|
||||||
|
browser = await browser_launcher.launch(headless=settings.headless)
|
||||||
|
context = await browser.new_context()
|
||||||
|
queue: Deque[Tuple[str, int]] = deque([(settings.root_url, 0)])
|
||||||
|
visited: Set[str] = set()
|
||||||
|
results: List[ScrapedLink] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
while queue and len(visited) < settings.max_pages:
|
||||||
|
url, depth = queue.popleft()
|
||||||
|
if url in visited or depth > settings.max_depth:
|
||||||
|
continue
|
||||||
|
visited.add(url)
|
||||||
|
|
||||||
|
page = await context.new_page()
|
||||||
|
try:
|
||||||
|
await page.goto(url, wait_until="domcontentloaded", timeout=20000)
|
||||||
|
except Exception:
|
||||||
|
await page.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
links = await extract_links(page)
|
||||||
|
for href, text in links:
|
||||||
|
normalized = normalize_url(url, href)
|
||||||
|
|
||||||
|
if matches_any(text, EXCLUSION_KEYWORDS) or matches_any(normalized, EXCLUSION_KEYWORDS):
|
||||||
|
continue
|
||||||
|
|
||||||
|
text_lower = text.lower()
|
||||||
|
normalized_lower = normalized.lower()
|
||||||
|
|
||||||
|
if matches_any(text_lower, PROGRAM_KEYWORDS) or matches_any(normalized_lower, PROGRAM_KEYWORDS):
|
||||||
|
results.append(
|
||||||
|
ScrapedLink(
|
||||||
|
url=normalized,
|
||||||
|
text=text,
|
||||||
|
source_url=url,
|
||||||
|
bucket="program",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if matches_any(text_lower, FACULTY_KEYWORDS) or matches_any(normalized_lower, FACULTY_KEYWORDS):
|
||||||
|
results.append(
|
||||||
|
ScrapedLink(
|
||||||
|
url=normalized,
|
||||||
|
text=text,
|
||||||
|
source_url=url,
|
||||||
|
bucket="faculty",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if depth < settings.max_depth:
|
||||||
|
queue.append((normalized, depth + 1))
|
||||||
|
await page.close()
|
||||||
|
finally:
|
||||||
|
await context.close()
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def serialize(results: List[ScrapedLink], target: Path, root_url: str) -> None:
|
||||||
|
payload = {
|
||||||
|
"root_url": root_url,
|
||||||
|
"program_links": [
|
||||||
|
{**asdict(link), "bucket": link.bucket} for link in results if link.bucket == "program"
|
||||||
|
],
|
||||||
|
"faculty_links": [
|
||||||
|
{**asdict(link), "bucket": link.bucket} for link in results if link.bucket == "faculty"
|
||||||
|
],
|
||||||
|
"notes": EXTRA_NOTES,
|
||||||
|
"metadata_fields": METADATA_FIELDS,
|
||||||
|
}
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
target.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Playwright scraper generated by the Agno agent for https://www.stanford.edu."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--root-url",
|
||||||
|
default="https://www.stanford.edu",
|
||||||
|
help="Seed url to start crawling from.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-depth",
|
||||||
|
type=int,
|
||||||
|
default=2,
|
||||||
|
help="Maximum crawl depth.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-pages",
|
||||||
|
type=int,
|
||||||
|
default=20,
|
||||||
|
help="Maximum number of pages to visit.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
default=Path("stanford-masters-faculty_masters.json"),
|
||||||
|
help="Where to save the JSON output.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--headless",
|
||||||
|
action="store_true",
|
||||||
|
default=True,
|
||||||
|
help="Run browser in headless mode (default: True).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-headless",
|
||||||
|
action="store_false",
|
||||||
|
dest="headless",
|
||||||
|
help="Run browser with visible window.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--browser",
|
||||||
|
choices=["chromium", "firefox", "webkit"],
|
||||||
|
default="chromium",
|
||||||
|
help="Browser engine to launch via Playwright.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
async def main_async() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
settings = ScrapeSettings(
|
||||||
|
root_url=args.root_url,
|
||||||
|
max_depth=args.max_depth,
|
||||||
|
max_pages=args.max_pages,
|
||||||
|
headless=args.headless,
|
||||||
|
output=args.output,
|
||||||
|
)
|
||||||
|
links = await crawl(settings, browser_name=args.browser)
|
||||||
|
serialize(links, settings.output, settings.root_url)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
asyncio.run(main_async())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
57
pyproject.toml
Normal file
57
pyproject.toml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
[project]
|
||||||
|
name = "university-agent"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Agno powered agent that generates Playwright scraping scripts for university program research."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "Codex Agent"}
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"agno>=2.3.8",
|
||||||
|
"typer>=0.12.5",
|
||||||
|
"pydantic>=2.9",
|
||||||
|
"pydantic-settings>=2.6",
|
||||||
|
"jinja2>=3.1",
|
||||||
|
"playwright>=1.48",
|
||||||
|
"httpx>=0.28",
|
||||||
|
"rich>=13.7"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"ruff>=0.6.9",
|
||||||
|
"ty>=0.0.1a32"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
university-agent = "university_agent.cli:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = {"" = "src"}
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
"university_agent" = ["templates/*.jinja"]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
managed = true
|
||||||
|
dev-dependencies = ["ruff>=0.6.9", "ty>=0.0.1a32"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "B", "I", "UP", "RUF"]
|
||||||
|
ignore = ["B008"] # Allow function calls in argument defaults (typer pattern)
|
||||||
|
|
||||||
|
[tool.ty.environment]
|
||||||
|
python-version = "3.12"
|
||||||
93
src/university_agent.egg-info/PKG-INFO
Normal file
93
src/university_agent.egg-info/PKG-INFO
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: university-agent
|
||||||
|
Version: 0.1.0
|
||||||
|
Summary: Agno powered agent that generates Playwright scraping scripts for university program research.
|
||||||
|
Author: Codex Agent
|
||||||
|
License: MIT
|
||||||
|
Requires-Python: >=3.11
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: agno>=2.3.8
|
||||||
|
Requires-Dist: typer>=0.12.5
|
||||||
|
Requires-Dist: pydantic>=2.9
|
||||||
|
Requires-Dist: pydantic-settings>=2.6
|
||||||
|
Requires-Dist: jinja2>=3.1
|
||||||
|
Requires-Dist: playwright>=1.48
|
||||||
|
Requires-Dist: httpx>=0.28
|
||||||
|
Requires-Dist: rich>=13.7
|
||||||
|
Provides-Extra: dev
|
||||||
|
Requires-Dist: ruff>=0.6.9; extra == "dev"
|
||||||
|
Requires-Dist: ty>=0.0.1a32; extra == "dev"
|
||||||
|
|
||||||
|
# University Playwright Codegen Agent
|
||||||
|
|
||||||
|
构建于 [Agno](https://docs.agno.com/) 的自动化代码生成代理:输入海外大学官网的根地址,即可生成一份使用 **Playwright** 的 Python 脚本,脚本会抓取各学院/研究生院下的硕士项目网址以及项目中列出的导师(Supervisor/Faculty)个人信息页面。本项目使用 `uv` 进行依赖管理,`ruff` 做静态检查,`ty` 负责类型检查,并提供了一个基于 Typer 的 CLI。
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ **Agno Agent**:利用 `output_schema` 强制结构化输出,里程碑式地生成 `ScriptPlan` 并将其渲染为可执行脚本。
|
||||||
|
- ✅ **Playwright sampling**:计划生成前会用 Playwright 对站点进行轻量抓取,帮助 Agent 找到关键词与导航策略。
|
||||||
|
- ✅ **Deterministic script template**:脚本模板包含 BFS 爬取、关键词过滤、JSON 输出等逻辑,确保满足“硕士项目 + 导师”需求。
|
||||||
|
- ✅ **uv + ruff + ty workflow**:开箱即用的现代 Python 工具链。
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
1. **创建虚拟环境并安装依赖**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv venv --python 3.12
|
||||||
|
uv pip install -r pyproject.toml
|
||||||
|
playwright install # 安装浏览器内核
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **配置大模型 API key**
|
||||||
|
|
||||||
|
- OpenAI: `export OPENAI_API_KEY=...`
|
||||||
|
- Anthropic: `export ANTHROPIC_API_KEY=...`
|
||||||
|
- 可通过环境变量 `CODEGEN_MODEL_PROVIDER` 在 `openai` 与 `anthropic` 之间切换。
|
||||||
|
|
||||||
|
3. **运行 CLI 生成脚本**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run university-agent generate \
|
||||||
|
"https://www.example.edu" \
|
||||||
|
--campus "Example Campus" \
|
||||||
|
--language "English" \
|
||||||
|
--max-depth 2 \
|
||||||
|
--max-pages 60
|
||||||
|
```
|
||||||
|
|
||||||
|
运行完成后会在 `artifacts/` 下看到生成的 Playwright 脚本,并在终端展示自动规划的关键词与验证步骤。
|
||||||
|
|
||||||
|
4. **执行 Ruff & Ty 检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run ruff check
|
||||||
|
uvx ty check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── README.md
|
||||||
|
├── pyproject.toml
|
||||||
|
├── src/university_agent
|
||||||
|
│ ├── agent.py # Agno Agent 配置
|
||||||
|
│ ├── cli.py # Typer CLI
|
||||||
|
│ ├── config.py # pydantic Settings
|
||||||
|
│ ├── generator.py # Orchestration 引擎
|
||||||
|
│ ├── models.py # 数据模型(请求/计划/结果)
|
||||||
|
│ ├── renderer.py # ScriptPlan -> Playwright script
|
||||||
|
│ ├── sampler.py # Playwright 采样
|
||||||
|
│ ├── templates/
|
||||||
|
│ │ └── playwright_script.py.jinja
|
||||||
|
│ └── writer.py # 将脚本写入 artifacts/
|
||||||
|
└── 任务1.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- `university-agent generate --help` 查看所有 CLI 选项,可选择跳过采样或导出规划 JSON。
|
||||||
|
- 如果 Agno Agent 需使用其他工具,可在 `agent.py` 中自行扩展自定义 `tool`。
|
||||||
|
- Playwright 采样在某些环境中需要额外的浏览器依赖,请根据官方提示执行 `playwright install`。
|
||||||
|
|
||||||
|
Happy building! 🎓🤖
|
||||||
18
src/university_agent.egg-info/SOURCES.txt
Normal file
18
src/university_agent.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
README.md
|
||||||
|
pyproject.toml
|
||||||
|
src/university_agent/__init__.py
|
||||||
|
src/university_agent/agent.py
|
||||||
|
src/university_agent/cli.py
|
||||||
|
src/university_agent/config.py
|
||||||
|
src/university_agent/generator.py
|
||||||
|
src/university_agent/models.py
|
||||||
|
src/university_agent/renderer.py
|
||||||
|
src/university_agent/sampler.py
|
||||||
|
src/university_agent/writer.py
|
||||||
|
src/university_agent.egg-info/PKG-INFO
|
||||||
|
src/university_agent.egg-info/SOURCES.txt
|
||||||
|
src/university_agent.egg-info/dependency_links.txt
|
||||||
|
src/university_agent.egg-info/entry_points.txt
|
||||||
|
src/university_agent.egg-info/requires.txt
|
||||||
|
src/university_agent.egg-info/top_level.txt
|
||||||
|
src/university_agent/templates/playwright_script.py.jinja
|
||||||
1
src/university_agent.egg-info/dependency_links.txt
Normal file
1
src/university_agent.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
2
src/university_agent.egg-info/entry_points.txt
Normal file
2
src/university_agent.egg-info/entry_points.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[console_scripts]
|
||||||
|
university-agent = university_agent.cli:main
|
||||||
12
src/university_agent.egg-info/requires.txt
Normal file
12
src/university_agent.egg-info/requires.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
agno>=2.3.8
|
||||||
|
typer>=0.12.5
|
||||||
|
pydantic>=2.9
|
||||||
|
pydantic-settings>=2.6
|
||||||
|
jinja2>=3.1
|
||||||
|
playwright>=1.48
|
||||||
|
httpx>=0.28
|
||||||
|
rich>=13.7
|
||||||
|
|
||||||
|
[dev]
|
||||||
|
ruff>=0.6.9
|
||||||
|
ty>=0.0.1a32
|
||||||
1
src/university_agent.egg-info/top_level.txt
Normal file
1
src/university_agent.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
university_agent
|
||||||
15
src/university_agent/__init__.py
Normal file
15
src/university_agent/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""Top-level package for the university code generation agent."""
|
||||||
|
|
||||||
|
from .agent import ScriptAgent
|
||||||
|
from .config import Settings
|
||||||
|
from .generator import GenerationEngine
|
||||||
|
from .models import GenerationRequest, GenerationResult, ScriptPlan
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"GenerationEngine",
|
||||||
|
"GenerationRequest",
|
||||||
|
"GenerationResult",
|
||||||
|
"ScriptAgent",
|
||||||
|
"ScriptPlan",
|
||||||
|
"Settings",
|
||||||
|
]
|
||||||
BIN
src/university_agent/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/agent.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/agent.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/cli.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/cli.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/config.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/generator.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/generator.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/models.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/renderer.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/renderer.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/sampler.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/sampler.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/university_agent/__pycache__/writer.cpython-312.pyc
Normal file
BIN
src/university_agent/__pycache__/writer.cpython-312.pyc
Normal file
Binary file not shown.
144
src/university_agent/agent.py
Normal file
144
src/university_agent/agent.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from agno.agent import Agent
|
||||||
|
from agno.models.anthropic import Claude
|
||||||
|
from agno.models.openai import OpenAIChat
|
||||||
|
from agno.run.agent import RunOutput
|
||||||
|
|
||||||
|
from .config import Settings
|
||||||
|
from .models import GenerationRequest, ScriptPlan, SiteSummary
|
||||||
|
|
||||||
|
BASE_INSTRUCTIONS = dedent(
|
||||||
|
"""
|
||||||
|
You are a senior automation engineer specializing in web scraping for academic research.
|
||||||
|
Your task is to generate production-grade Playwright scraping plans that collect:
|
||||||
|
|
||||||
|
1. **Master's program pages**: Landing pages for graduate/master's degree programs.
|
||||||
|
- Look for URLs containing: /programs/, /graduate/, /masters/, /degrees/, /academics/
|
||||||
|
- Keywords in text: "Master of", "M.S.", "M.A.", "graduate program", "postgraduate"
|
||||||
|
|
||||||
|
2. **Individual faculty/supervisor profile pages**: Personal pages of professors and researchers.
|
||||||
|
- IMPORTANT: We need INDIVIDUAL profile pages, NOT directory listings or department pages.
|
||||||
|
- Profile URL patterns: /people/firstname-lastname, /~username, /profile/, /faculty/name
|
||||||
|
- The goal is to find pages like "Dr. John Smith" not "Faculty Directory"
|
||||||
|
|
||||||
|
Key requirements:
|
||||||
|
- Prioritize crawling faculty/people directory pages to find individual profiles
|
||||||
|
- Use specific keywords that distinguish individual profiles from listing pages
|
||||||
|
- Include URL patterns that indicate personal pages (e.g., /people/*, /profile/*, /~*)
|
||||||
|
- Exclude general navigation pages (admissions, news, events, careers, login)
|
||||||
|
|
||||||
|
Output must follow the ScriptPlan schema exactly with specific, actionable keywords.
|
||||||
|
"""
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
PROMPT_TEMPLATE = dedent(
|
||||||
|
"""
|
||||||
|
## University context
|
||||||
|
- Target url: {target_url}
|
||||||
|
- Campus or nickname: {campus}
|
||||||
|
- Assumed language: {language}
|
||||||
|
- Crawl caps requested by user: depth={depth}, pages={pages}
|
||||||
|
- Custom notes: {notes}
|
||||||
|
|
||||||
|
## Browser snapshot (Playwright)
|
||||||
|
{snapshot}
|
||||||
|
|
||||||
|
## Your task
|
||||||
|
Produce a ScriptPlan with these requirements:
|
||||||
|
|
||||||
|
1. **script_name**: Use `{default_script}` or infer a better name.
|
||||||
|
|
||||||
|
2. **master_program_keywords** (6-8 keywords): Terms that identify master's program pages.
|
||||||
|
- Include both URL fragments (e.g., "/graduate/", "/masters/") and text patterns
|
||||||
|
- Be specific to this university's naming conventions based on the snapshot
|
||||||
|
|
||||||
|
3. **faculty_keywords** (6-8 keywords): Terms that identify INDIVIDUAL faculty profile pages.
|
||||||
|
- Focus on patterns that indicate a PERSON's page, not a directory
|
||||||
|
- Include: professor names patterns, "/people/", "/profile/", "Dr.", "Professor"
|
||||||
|
- Look at the snapshot to identify how this university structures faculty URLs
|
||||||
|
|
||||||
|
4. **exclusion_keywords** (8-12 keywords): Aggressively exclude non-relevant pages.
|
||||||
|
- Include: admissions, apply, tuition, news, events, careers, jobs, login, donate, alumni
|
||||||
|
- Add university-specific noise pages you spot in the snapshot
|
||||||
|
|
||||||
|
5. **navigation_strategy**: Describe how to efficiently find individual profiles.
|
||||||
|
- Identify the path from homepage -> department -> faculty directory -> individual profiles
|
||||||
|
- Note any subdomains (e.g., cs.university.edu, engineering.university.edu)
|
||||||
|
|
||||||
|
6. **verification_steps**: How to verify the results are correct.
|
||||||
|
|
||||||
|
7. **extra_notes**: Important observations about the site structure.
|
||||||
|
|
||||||
|
Respond with valid JSON only.
|
||||||
|
"""
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptAgent:
|
||||||
|
"""Thin wrapper around Agno's Agent configured for our structured planning task."""
|
||||||
|
|
||||||
|
def __init__(self, settings: Settings):
|
||||||
|
self.settings = settings
|
||||||
|
self._agent = Agent(
|
||||||
|
name="UniversityScriptSmith",
|
||||||
|
description="Produces structured plans for Playwright-based scrapers.",
|
||||||
|
instructions=[BASE_INSTRUCTIONS],
|
||||||
|
model=self._resolve_model(),
|
||||||
|
output_schema=ScriptPlan,
|
||||||
|
reasoning=settings.reasoning_enabled,
|
||||||
|
reasoning_max_steps=settings.reasoning_max_steps,
|
||||||
|
add_history_to_context=False,
|
||||||
|
markdown=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _resolve_model(self):
|
||||||
|
provider = self.settings.model_provider.lower()
|
||||||
|
if provider == "anthropic":
|
||||||
|
return Claude(id=self.settings.anthropic_model)
|
||||||
|
if provider == "openai":
|
||||||
|
return OpenAIChat(id=self.settings.openai_model)
|
||||||
|
raise ValueError(f"Unsupported provider: {provider}")
|
||||||
|
|
||||||
|
def build_plan(self, request: GenerationRequest, summary: SiteSummary | None) -> ScriptPlan:
|
||||||
|
"""Call the Agno agent and coerce the structured response into our Pydantic model."""
|
||||||
|
|
||||||
|
snapshot = summary.to_prompt_section() if summary else "No snapshot available."
|
||||||
|
|
||||||
|
prompt = PROMPT_TEMPLATE.format(
|
||||||
|
target_url=request.target_url,
|
||||||
|
campus=request.campus_name or "unspecified",
|
||||||
|
language=request.assumed_language or "unspecified",
|
||||||
|
depth=request.max_depth,
|
||||||
|
pages=request.max_pages,
|
||||||
|
notes=request.notes or "n/a",
|
||||||
|
snapshot=snapshot,
|
||||||
|
default_script=self.settings.default_script_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
run_response = self._agent.run(prompt)
|
||||||
|
plan = self._coerce_plan(run_response)
|
||||||
|
if not plan.project_slug:
|
||||||
|
fallback = (request.campus_name or "university").replace(" ", "-").lower()
|
||||||
|
plan.project_slug = fallback
|
||||||
|
if not plan.script_name:
|
||||||
|
plan.script_name = self.settings.default_script_name
|
||||||
|
return plan
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _coerce_plan(run_response: RunOutput) -> ScriptPlan:
|
||||||
|
content = run_response.content
|
||||||
|
if isinstance(content, ScriptPlan):
|
||||||
|
return content
|
||||||
|
if isinstance(content, dict):
|
||||||
|
return ScriptPlan.model_validate(content)
|
||||||
|
if isinstance(content, str):
|
||||||
|
try:
|
||||||
|
payload = json.loads(content)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError("Agent returned a non-JSON payload.") from exc
|
||||||
|
return ScriptPlan.model_validate(payload)
|
||||||
|
raise ValueError("Agent response did not match the ScriptPlan schema.")
|
||||||
85
src/university_agent/cli.py
Normal file
85
src/university_agent/cli.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
from .config import Settings
|
||||||
|
from .generator import GenerationEngine
|
||||||
|
from .models import GenerationRequest
|
||||||
|
|
||||||
|
app = typer.Typer(help="Generate Playwright scraping scripts for overseas universities.")
|
||||||
|
|
||||||
|
|
||||||
|
def _print_plan(result) -> None:
|
||||||
|
console = Console()
|
||||||
|
table = Table(title="Script Plan", show_lines=False)
|
||||||
|
table.add_column("Key", style="cyan", no_wrap=True)
|
||||||
|
table.add_column("Value", style="white")
|
||||||
|
plan = result.plan
|
||||||
|
table.add_row("Script name", plan.script_name)
|
||||||
|
table.add_row("Project slug", plan.project_slug)
|
||||||
|
table.add_row("Master keywords", ", ".join(plan.master_program_keywords))
|
||||||
|
table.add_row("Faculty keywords", ", ".join(plan.faculty_keywords))
|
||||||
|
table.add_row("Exclude keywords", ", ".join(plan.exclusion_keywords) or "n/a")
|
||||||
|
table.add_row("Metadata fields", ", ".join(plan.metadata_fields))
|
||||||
|
table.add_row("Navigation strategy", plan.navigation_strategy)
|
||||||
|
table.add_row("Verification steps", "\n".join(plan.verification_steps) or "n/a")
|
||||||
|
if plan.extra_notes:
|
||||||
|
table.add_row("Extra notes", "\n".join(plan.extra_notes))
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def generate(
|
||||||
|
url: str = typer.Argument(..., help="Root URL of the university site."),
|
||||||
|
campus: str | None = typer.Option(None, "--campus", help="Campus or locale nickname."),
|
||||||
|
language: str | None = typer.Option(None, "--language", help="Primary site language."),
|
||||||
|
notes: str | None = typer.Option(None, "--notes", help="Extra requirements for the script."),
|
||||||
|
max_depth: int = typer.Option(2, "--max-depth", min=1, max=5, help="Depth limit for crawling."),
|
||||||
|
max_pages: int = typer.Option(
|
||||||
|
40, "--max-pages", min=10, max=200, help="Page budget for the generated script."
|
||||||
|
),
|
||||||
|
no_snapshot: bool = typer.Option(
|
||||||
|
False, "--no-snapshot", help="Skip the Playwright site sampling step."
|
||||||
|
),
|
||||||
|
export_plan: Path | None = typer.Option(
|
||||||
|
None, "--export-plan", help="Optional path to persist the structured plan as JSON."
|
||||||
|
),
|
||||||
|
) -> None:
|
||||||
|
"""Generate a tailored Playwright script for the provided university website."""
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
|
engine = GenerationEngine(settings)
|
||||||
|
request = GenerationRequest(
|
||||||
|
target_url=url,
|
||||||
|
campus_name=campus,
|
||||||
|
assumed_language=language,
|
||||||
|
notes=notes,
|
||||||
|
max_depth=max_depth,
|
||||||
|
max_pages=max_pages,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = engine.generate(request, capture_snapshot=not no_snapshot)
|
||||||
|
console = Console()
|
||||||
|
console.print(f"[green]Script saved to[/green] {result.script_path}")
|
||||||
|
_print_plan(result)
|
||||||
|
|
||||||
|
if export_plan:
|
||||||
|
export_plan.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
export_plan.write_text(
|
||||||
|
json.dumps(result.plan.model_dump(mode="json"), indent=2),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
console.print(f"[blue]Plan exported to[/blue] {export_plan}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
72
src/university_agent/config.py
Normal file
72
src/university_agent/config.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
"""Runtime configuration for the code-generation agent."""
|
||||||
|
|
||||||
|
model_provider: Literal["anthropic", "openai"] = Field(
|
||||||
|
default="anthropic",
|
||||||
|
description="LLM provider consumed through the Agno SDK.",
|
||||||
|
)
|
||||||
|
anthropic_model: str = Field(
|
||||||
|
default="claude-opus-4-5-20251101",
|
||||||
|
description="Default Anthropic model identifier.",
|
||||||
|
)
|
||||||
|
openai_model: str = Field(
|
||||||
|
default="o4-mini",
|
||||||
|
description="Default OpenAI model identifier.",
|
||||||
|
)
|
||||||
|
reasoning_enabled: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="Enable multi-step reasoning for higher-fidelity plans.",
|
||||||
|
)
|
||||||
|
reasoning_max_steps: int = Field(
|
||||||
|
default=12,
|
||||||
|
ge=1,
|
||||||
|
le=20,
|
||||||
|
description="Cap on how many explicit reasoning turns the agent can request.",
|
||||||
|
)
|
||||||
|
sample_pages: int = Field(
|
||||||
|
default=4,
|
||||||
|
ge=1,
|
||||||
|
le=25,
|
||||||
|
description="How many unique pages Playwright should snapshot before planning.",
|
||||||
|
)
|
||||||
|
sample_depth: int = Field(
|
||||||
|
default=2,
|
||||||
|
ge=1,
|
||||||
|
le=5,
|
||||||
|
description="Maximum depth of the limited site crawl performed for context.",
|
||||||
|
)
|
||||||
|
playwright_browser: Literal["chromium", "firefox", "webkit"] = Field(
|
||||||
|
default="chromium",
|
||||||
|
description="Browser engine used by the Playwright-powered sampler.",
|
||||||
|
)
|
||||||
|
playwright_headless: bool = Field(
|
||||||
|
default=True, description="Run Playwright in headless mode when sampling."
|
||||||
|
)
|
||||||
|
navigation_timeout_ms: int = Field(
|
||||||
|
default=20000,
|
||||||
|
ge=5000,
|
||||||
|
le=60000,
|
||||||
|
description="Timeout applied to each Playwright navigation call.",
|
||||||
|
)
|
||||||
|
output_dir: Path = Field(
|
||||||
|
default=Path("artifacts"),
|
||||||
|
description="Directory where generated scripts will be stored.",
|
||||||
|
)
|
||||||
|
default_script_name: str = Field(
|
||||||
|
default="university_playwright_scraper.py",
|
||||||
|
description="Fallback script name when the agent omits a filename.",
|
||||||
|
)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_prefix = "CODEGEN_"
|
||||||
|
env_file = ".env"
|
||||||
|
case_sensitive = False
|
||||||
57
src/university_agent/generator.py
Normal file
57
src/university_agent/generator.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
from .agent import ScriptAgent
|
||||||
|
from .config import Settings
|
||||||
|
from .models import GenerationRequest, GenerationResult, SiteSummary
|
||||||
|
from .renderer import ScriptRenderer
|
||||||
|
from .sampler import SiteSampler
|
||||||
|
from .writer import ScriptWriter
|
||||||
|
|
||||||
|
|
||||||
|
class GenerationEngine:
|
||||||
|
"""High-level orchestration that stitches together the sampler, agent, and renderer."""
|
||||||
|
|
||||||
|
def __init__(self, settings: Settings | None = None):
|
||||||
|
self.settings = settings or Settings()
|
||||||
|
self.console = Console()
|
||||||
|
self.sampler = SiteSampler(self.settings)
|
||||||
|
self.agent = ScriptAgent(self.settings)
|
||||||
|
self.renderer = ScriptRenderer()
|
||||||
|
self.writer = ScriptWriter(self.settings)
|
||||||
|
|
||||||
|
def _maybe_snapshot(self, request: GenerationRequest, capture: bool) -> SiteSummary | None:
|
||||||
|
if not capture:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
self.console.log(
|
||||||
|
f"[cyan]Sampling[/cyan] {request.target_url} "
|
||||||
|
f"(pages={self.settings.sample_pages}, depth={self.settings.sample_depth})"
|
||||||
|
)
|
||||||
|
return self.sampler.snapshot(
|
||||||
|
request.target_url,
|
||||||
|
max_pages=self.settings.sample_pages,
|
||||||
|
max_depth=self.settings.sample_depth,
|
||||||
|
)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
self.console.log(
|
||||||
|
Text("Playwright sampling failed. Continuing without snapshot.", style="yellow")
|
||||||
|
)
|
||||||
|
self.console.log(str(exc))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def generate(
|
||||||
|
self, request: GenerationRequest, *, capture_snapshot: bool = True
|
||||||
|
) -> GenerationResult:
|
||||||
|
summary = self._maybe_snapshot(request, capture=capture_snapshot)
|
||||||
|
plan = self.agent.build_plan(request, summary)
|
||||||
|
script_body = self.renderer.render(plan, request, summary)
|
||||||
|
artifact = self.writer.write(plan, script_body)
|
||||||
|
return GenerationResult(
|
||||||
|
plan=plan,
|
||||||
|
request=request,
|
||||||
|
summary=summary,
|
||||||
|
script_path=artifact,
|
||||||
|
)
|
||||||
132
src/university_agent/models.py
Normal file
132
src/university_agent/models.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
|
||||||
|
|
||||||
|
|
||||||
|
class GenerationRequest(BaseModel):
|
||||||
|
"""User intent passed to the agent when creating a scraper."""
|
||||||
|
|
||||||
|
target_url: HttpUrl | str
|
||||||
|
campus_name: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Optional human readable name for the campus or region.",
|
||||||
|
)
|
||||||
|
assumed_language: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="Language hint to bias prompts and keyword selection.",
|
||||||
|
)
|
||||||
|
notes: str | None = Field(
|
||||||
|
default=None, description="Any speciality requirements for the scraper."
|
||||||
|
)
|
||||||
|
max_depth: int = Field(
|
||||||
|
default=2, ge=1, le=5, description="Maximum crawl depth for generated script."
|
||||||
|
)
|
||||||
|
max_pages: int = Field(
|
||||||
|
default=32,
|
||||||
|
ge=5,
|
||||||
|
le=200,
|
||||||
|
description="Safety cap for how many pages the script should traverse.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorSample(BaseModel):
|
||||||
|
text: str
|
||||||
|
href: str
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSample(BaseModel):
|
||||||
|
"""Summarised snapshot of a single page captured through Playwright."""
|
||||||
|
|
||||||
|
url: HttpUrl | str
|
||||||
|
title: str
|
||||||
|
snippet: str
|
||||||
|
anchors: list[AnchorSample] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class SiteSummary(BaseModel):
|
||||||
|
target_url: HttpUrl | str
|
||||||
|
pages: list[SiteSample] = Field(default_factory=list)
|
||||||
|
truncated: bool = False
|
||||||
|
|
||||||
|
def to_prompt_section(self) -> str:
|
||||||
|
"""Render the summary as a markdown friendly text block."""
|
||||||
|
if not self.pages:
|
||||||
|
return "No browser snapshot was captured."
|
||||||
|
|
||||||
|
parts: list[str] = []
|
||||||
|
for idx, sample in enumerate(self.pages, start=1):
|
||||||
|
anchors = ", ".join(
|
||||||
|
f"{a.text.strip()[:64]} -> {a.href}" for a in sample.anchors[:6]
|
||||||
|
)
|
||||||
|
parts.append(
|
||||||
|
f"{idx}. {sample.title.strip()} ({sample.url})\n"
|
||||||
|
f" Snippet: {sample.snippet.strip()[:280]}\n"
|
||||||
|
f" Anchors: {anchors if anchors else 'n/a'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
note = "\nSnapshot truncated." if self.truncated else ""
|
||||||
|
return "\n".join(parts) + note
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptPlan(BaseModel):
|
||||||
|
"""Structured artifact we expect from the Agno agent."""
|
||||||
|
|
||||||
|
script_name: str = Field(
|
||||||
|
default="university_playwright_scraper.py",
|
||||||
|
description="Filename for the generated script.",
|
||||||
|
)
|
||||||
|
project_slug: str = Field(
|
||||||
|
description="A short kebab-case slug describing the university target."
|
||||||
|
)
|
||||||
|
description: str
|
||||||
|
master_program_keywords: list[str]
|
||||||
|
faculty_keywords: list[str]
|
||||||
|
exclusion_keywords: list[str] = Field(
|
||||||
|
default_factory=lambda: [
|
||||||
|
"admissions", "apply", "tuition", "financial aid", "login",
|
||||||
|
"news", "events", "alumni", "athletics", "giving", "donate",
|
||||||
|
"careers", "jobs", "contact", "sitemap"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
metadata_fields: list[str] = Field(
|
||||||
|
default_factory=lambda: [
|
||||||
|
"url", "title", "entity_type", "department", "email", "scraped_at"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
navigation_strategy: str
|
||||||
|
output_format: str = Field(
|
||||||
|
default="jsonl", description="Format used by the generated script."
|
||||||
|
)
|
||||||
|
verification_steps: list[str] = Field(default_factory=list)
|
||||||
|
extra_notes: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
model_config = ConfigDict(validate_assignment=True)
|
||||||
|
|
||||||
|
@field_validator("script_name")
|
||||||
|
@classmethod
|
||||||
|
def ensure_python_extension(cls, value: str) -> str:
|
||||||
|
if not value.endswith(".py"):
|
||||||
|
return f"{value}.py"
|
||||||
|
return value
|
||||||
|
|
||||||
|
@field_validator("project_slug")
|
||||||
|
@classmethod
|
||||||
|
def enforce_slug(cls, value: str) -> str:
|
||||||
|
return value.strip().replace(" ", "-").lower()
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratedScript(BaseModel):
|
||||||
|
plan: ScriptPlan
|
||||||
|
request: GenerationRequest
|
||||||
|
summary: SiteSummary | None = None
|
||||||
|
script_body: str
|
||||||
|
artifact_path: Path
|
||||||
|
|
||||||
|
|
||||||
|
class GenerationResult(BaseModel):
|
||||||
|
plan: ScriptPlan
|
||||||
|
request: GenerationRequest
|
||||||
|
summary: SiteSummary | None = None
|
||||||
|
script_path: Path
|
||||||
42
src/university_agent/renderer.py
Normal file
42
src/university_agent/renderer.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from importlib import resources
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from jinja2 import BaseLoader, Environment
|
||||||
|
|
||||||
|
from .models import GenerationRequest, ScriptPlan, SiteSummary
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptRenderer:
|
||||||
|
"""Render a deterministic Python script using a Jinja template."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
template_text = resources.files("university_agent.templates").joinpath(
|
||||||
|
"playwright_script.py.jinja"
|
||||||
|
).read_text(encoding="utf-8")
|
||||||
|
self._env = Environment(
|
||||||
|
loader=BaseLoader(), trim_blocks=True, lstrip_blocks=True, autoescape=False
|
||||||
|
)
|
||||||
|
self._template = self._env.from_string(template_text)
|
||||||
|
|
||||||
|
def render(
|
||||||
|
self, plan: ScriptPlan, request: GenerationRequest, summary: SiteSummary | None
|
||||||
|
) -> str:
|
||||||
|
generated_at = datetime.now(tz=UTC).isoformat()
|
||||||
|
summary_text = summary.to_prompt_section() if summary else "No snapshot."
|
||||||
|
|
||||||
|
return self._template.render(
|
||||||
|
plan=plan,
|
||||||
|
request=request,
|
||||||
|
summary_text=summary_text,
|
||||||
|
generated_at=generated_at,
|
||||||
|
header=dedent(
|
||||||
|
f"""
|
||||||
|
Auto-generated by the Agno codegen agent.
|
||||||
|
Target university: {request.campus_name or 'unspecified'} ({request.target_url})
|
||||||
|
Requested caps: depth={request.max_depth}, pages={request.max_pages}
|
||||||
|
"""
|
||||||
|
).strip(),
|
||||||
|
)
|
||||||
107
src/university_agent/sampler.py
Normal file
107
src/university_agent/sampler.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from collections import deque
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from urllib.parse import urldefrag, urljoin
|
||||||
|
|
||||||
|
from pydantic import HttpUrl
|
||||||
|
|
||||||
|
from .config import Settings
|
||||||
|
from .models import AnchorSample, SiteSample, SiteSummary
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_url(base: str, raw: str) -> str:
|
||||||
|
absolute = urljoin(base, raw)
|
||||||
|
cleaned, _ = urldefrag(absolute)
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SiteSampler:
|
||||||
|
"""Lightweight Playwright-powered crawler used to prime the agent."""
|
||||||
|
|
||||||
|
settings: Settings
|
||||||
|
|
||||||
|
async def _gather(self, target: HttpUrl | str, max_pages: int, max_depth: int) -> SiteSummary:
|
||||||
|
try:
|
||||||
|
from playwright.async_api import async_playwright
|
||||||
|
except ImportError as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Playwright is required for sampling but is not installed. "
|
||||||
|
"Install it with `uv pip install playwright` and `playwright install`."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
summary = SiteSummary(target_url=target)
|
||||||
|
visited: set[str] = set()
|
||||||
|
queue: deque[tuple[str, int]] = deque([(str(target), 0)])
|
||||||
|
|
||||||
|
async with async_playwright() as p:
|
||||||
|
browser_launcher = getattr(p, self.settings.playwright_browser)
|
||||||
|
browser = await browser_launcher.launch(headless=self.settings.playwright_headless)
|
||||||
|
context = await browser.new_context()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while queue and len(summary.pages) < max_pages:
|
||||||
|
url, depth = queue.popleft()
|
||||||
|
if url in visited or depth > max_depth:
|
||||||
|
continue
|
||||||
|
visited.add(url)
|
||||||
|
page = await context.new_page()
|
||||||
|
try:
|
||||||
|
timeout = self.settings.navigation_timeout_ms
|
||||||
|
await page.goto(url, wait_until="domcontentloaded", timeout=timeout)
|
||||||
|
except Exception:
|
||||||
|
await page.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
title = await page.title()
|
||||||
|
text_timeout = self.settings.navigation_timeout_ms // 2
|
||||||
|
snippet = await page.inner_text("body", timeout=text_timeout)
|
||||||
|
snippet = snippet.replace("\n", " ").strip()
|
||||||
|
snippet = snippet[:500]
|
||||||
|
anchors_raw: Iterable[dict] = await page.eval_on_selector_all(
|
||||||
|
"a",
|
||||||
|
"""elements => elements
|
||||||
|
.map(el => ({text: (el.textContent || '').trim(), href: el.href}))
|
||||||
|
.filter(item => item.href && item.text)""",
|
||||||
|
)
|
||||||
|
anchors: list[AnchorSample] = []
|
||||||
|
for anchor in anchors_raw:
|
||||||
|
if len(anchors) >= 12:
|
||||||
|
break
|
||||||
|
anchors.append(AnchorSample(text=anchor["text"][:80], href=anchor["href"]))
|
||||||
|
if depth < max_depth:
|
||||||
|
queue.append((_normalize_url(url, anchor["href"]), depth + 1))
|
||||||
|
|
||||||
|
summary.pages.append(
|
||||||
|
SiteSample(
|
||||||
|
url=url,
|
||||||
|
title=title or url,
|
||||||
|
snippet=snippet or "n/a",
|
||||||
|
anchors=anchors,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await page.close()
|
||||||
|
finally:
|
||||||
|
await context.close()
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
summary.truncated = len(summary.pages) >= max_pages and bool(queue)
|
||||||
|
return summary
|
||||||
|
|
||||||
|
def snapshot(self, target: HttpUrl | str, *, max_pages: int, max_depth: int) -> SiteSummary:
|
||||||
|
"""Collect a synchronous snapshot by spinning up an event loop if needed."""
|
||||||
|
|
||||||
|
async def runner() -> SiteSummary:
|
||||||
|
return await self._gather(target, max_pages=max_pages, max_depth=max_depth)
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.get_running_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
return asyncio.run(runner())
|
||||||
|
raise RuntimeError(
|
||||||
|
"The Playwright sampler cannot run inside an active asyncio loop. "
|
||||||
|
"Call the private `_gather` coroutine directly if you need async integration."
|
||||||
|
)
|
||||||
423
src/university_agent/templates/playwright_script.py.jinja
Normal file
423
src/university_agent/templates/playwright_script.py.jinja
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
{{ header }}
|
||||||
|
|
||||||
|
Plan description: {{ plan.description }}
|
||||||
|
Navigation strategy: {{ plan.navigation_strategy }}
|
||||||
|
Verification checklist:
|
||||||
|
{% for step in plan.verification_steps -%}
|
||||||
|
- {{ step }}
|
||||||
|
{% endfor -%}
|
||||||
|
|
||||||
|
Playwright snapshot used to guide this plan:
|
||||||
|
{{ summary_text }}
|
||||||
|
|
||||||
|
Generated at: {{ generated_at }}
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Deque, Iterable, List, Set, Tuple
|
||||||
|
from urllib.parse import urljoin, urldefrag, urlparse
|
||||||
|
|
||||||
|
from playwright.async_api import async_playwright, Page, Response
|
||||||
|
|
||||||
|
PROGRAM_KEYWORDS = {{ plan.master_program_keywords }}
|
||||||
|
FACULTY_KEYWORDS = {{ plan.faculty_keywords }}
|
||||||
|
EXCLUSION_KEYWORDS = {{ plan.exclusion_keywords }}
|
||||||
|
METADATA_FIELDS = {{ plan.metadata_fields }}
|
||||||
|
EXTRA_NOTES = {{ plan.extra_notes }}
|
||||||
|
|
||||||
|
# URL patterns that indicate individual profile pages
|
||||||
|
PROFILE_URL_PATTERNS = [
|
||||||
|
"/people/", "/person/", "/profile/", "/profiles/",
|
||||||
|
"/faculty/", "/staff/", "/directory/",
|
||||||
|
"/~", # Unix-style personal pages
|
||||||
|
"/bio/", "/about/",
|
||||||
|
]
|
||||||
|
|
||||||
|
# URL patterns that indicate listing/directory pages (should be crawled deeper)
|
||||||
|
DIRECTORY_URL_PATTERNS = [
|
||||||
|
"/faculty", "/people", "/directory", "/staff",
|
||||||
|
"/team", "/members", "/researchers",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_url(base: str, href: str) -> str:
|
||||||
|
"""Normalize URL by resolving relative paths and removing fragments."""
|
||||||
|
absolute = urljoin(base, href)
|
||||||
|
cleaned, _ = urldefrag(absolute)
|
||||||
|
# Remove trailing slash for consistency
|
||||||
|
return cleaned.rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
def matches_any(text: str, keywords: Iterable[str]) -> bool:
|
||||||
|
"""Check if text contains any of the keywords (case-insensitive)."""
|
||||||
|
lowered = text.lower()
|
||||||
|
return any(keyword.lower() in lowered for keyword in keywords)
|
||||||
|
|
||||||
|
|
||||||
|
def is_same_domain(url1: str, url2: str) -> bool:
|
||||||
|
"""Check if two URLs belong to the same root domain."""
|
||||||
|
domain1 = urlparse(url1).netloc.replace("www.", "")
|
||||||
|
domain2 = urlparse(url2).netloc.replace("www.", "")
|
||||||
|
# Allow subdomains of the same root domain
|
||||||
|
parts1 = domain1.split(".")
|
||||||
|
parts2 = domain2.split(".")
|
||||||
|
if len(parts1) >= 2 and len(parts2) >= 2:
|
||||||
|
return parts1[-2:] == parts2[-2:]
|
||||||
|
return domain1 == domain2
|
||||||
|
|
||||||
|
|
||||||
|
def is_profile_url(url: str) -> bool:
|
||||||
|
"""Check if URL pattern suggests an individual profile page."""
|
||||||
|
url_lower = url.lower()
|
||||||
|
return any(pattern in url_lower for pattern in PROFILE_URL_PATTERNS)
|
||||||
|
|
||||||
|
|
||||||
|
def is_directory_url(url: str) -> bool:
|
||||||
|
"""Check if URL pattern suggests a directory/listing page."""
|
||||||
|
url_lower = url.lower()
|
||||||
|
return any(pattern in url_lower for pattern in DIRECTORY_URL_PATTERNS)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScrapedLink:
|
||||||
|
url: str
|
||||||
|
title: str
|
||||||
|
text: str
|
||||||
|
source_url: str
|
||||||
|
bucket: str # "program" or "faculty"
|
||||||
|
is_verified: bool = False
|
||||||
|
http_status: int = 0
|
||||||
|
is_profile_page: bool = False
|
||||||
|
scraped_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScrapeSettings:
|
||||||
|
root_url: str
|
||||||
|
max_depth: int
|
||||||
|
max_pages: int
|
||||||
|
headless: bool
|
||||||
|
output: Path
|
||||||
|
verify_links: bool = True
|
||||||
|
request_delay: float = 1.0 # Polite crawling delay
|
||||||
|
|
||||||
|
|
||||||
|
async def extract_links(page: Page) -> List[Tuple[str, str]]:
|
||||||
|
"""Extract all anchor links from the page."""
|
||||||
|
anchors: Iterable[dict] = await page.eval_on_selector_all(
|
||||||
|
"a",
|
||||||
|
"""elements => elements
|
||||||
|
.map(el => ({text: (el.textContent || '').trim(), href: el.href}))
|
||||||
|
.filter(item => item.text && item.href && item.href.startsWith('http'))""",
|
||||||
|
)
|
||||||
|
return [(item["href"], item["text"]) for item in anchors]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_page_title(page: Page) -> str:
|
||||||
|
"""Get the page title safely."""
|
||||||
|
try:
|
||||||
|
return await page.title() or ""
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def verify_link(context, url: str, timeout: int = 10000) -> Tuple[bool, int, str]:
|
||||||
|
"""
|
||||||
|
Verify a link by making a HEAD-like request.
|
||||||
|
Returns: (is_valid, status_code, page_title)
|
||||||
|
"""
|
||||||
|
page = await context.new_page()
|
||||||
|
try:
|
||||||
|
response: Response = await page.goto(url, wait_until="domcontentloaded", timeout=timeout)
|
||||||
|
if response:
|
||||||
|
status = response.status
|
||||||
|
title = await get_page_title(page)
|
||||||
|
is_valid = 200 <= status < 400
|
||||||
|
return is_valid, status, title
|
||||||
|
return False, 0, ""
|
||||||
|
except Exception:
|
||||||
|
return False, 0, ""
|
||||||
|
finally:
|
||||||
|
await page.close()
|
||||||
|
|
||||||
|
|
||||||
|
async def crawl(settings: ScrapeSettings, browser_name: str) -> List[ScrapedLink]:
|
||||||
|
"""
|
||||||
|
Crawl the website using BFS, collecting program and faculty links.
|
||||||
|
Features:
|
||||||
|
- URL deduplication
|
||||||
|
- Link verification
|
||||||
|
- Profile page detection
|
||||||
|
- Polite crawling with delays
|
||||||
|
"""
|
||||||
|
async with async_playwright() as p:
|
||||||
|
browser_launcher = getattr(p, browser_name)
|
||||||
|
browser = await browser_launcher.launch(headless=settings.headless)
|
||||||
|
context = await browser.new_context()
|
||||||
|
|
||||||
|
# Priority queue: (priority, url, depth) - lower priority = processed first
|
||||||
|
# Directory pages get priority 0, others get priority 1
|
||||||
|
queue: Deque[Tuple[int, str, int]] = deque([(0, settings.root_url, 0)])
|
||||||
|
visited: Set[str] = set()
|
||||||
|
found_urls: Set[str] = set() # For deduplication of results
|
||||||
|
results: List[ScrapedLink] = []
|
||||||
|
|
||||||
|
print(f"Starting crawl from: {settings.root_url}")
|
||||||
|
print(f"Max depth: {settings.max_depth}, Max pages: {settings.max_pages}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while queue and len(visited) < settings.max_pages:
|
||||||
|
# Sort queue by priority (directory pages first)
|
||||||
|
queue = deque(sorted(queue, key=lambda x: x[0]))
|
||||||
|
priority, url, depth = queue.popleft()
|
||||||
|
|
||||||
|
normalized_url = normalize_url(settings.root_url, url)
|
||||||
|
if normalized_url in visited or depth > settings.max_depth:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Only crawl same-domain URLs
|
||||||
|
if not is_same_domain(settings.root_url, normalized_url):
|
||||||
|
continue
|
||||||
|
|
||||||
|
visited.add(normalized_url)
|
||||||
|
print(f"[{len(visited)}/{settings.max_pages}] Depth {depth}: {normalized_url[:80]}...")
|
||||||
|
|
||||||
|
page = await context.new_page()
|
||||||
|
try:
|
||||||
|
response = await page.goto(
|
||||||
|
normalized_url, wait_until="domcontentloaded", timeout=20000
|
||||||
|
)
|
||||||
|
if not response or response.status >= 400:
|
||||||
|
await page.close()
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Error: {e}")
|
||||||
|
await page.close()
|
||||||
|
continue
|
||||||
|
|
||||||
|
page_title = await get_page_title(page)
|
||||||
|
links = await extract_links(page)
|
||||||
|
|
||||||
|
for href, text in links:
|
||||||
|
normalized_href = normalize_url(normalized_url, href)
|
||||||
|
|
||||||
|
# Skip if already found or is excluded
|
||||||
|
if normalized_href in found_urls:
|
||||||
|
continue
|
||||||
|
if matches_any(text, EXCLUSION_KEYWORDS) or matches_any(normalized_href, EXCLUSION_KEYWORDS):
|
||||||
|
continue
|
||||||
|
|
||||||
|
text_lower = text.lower()
|
||||||
|
href_lower = normalized_href.lower()
|
||||||
|
is_profile = is_profile_url(normalized_href)
|
||||||
|
|
||||||
|
# Check for program links
|
||||||
|
if matches_any(text_lower, PROGRAM_KEYWORDS) or matches_any(href_lower, PROGRAM_KEYWORDS):
|
||||||
|
found_urls.add(normalized_href)
|
||||||
|
results.append(
|
||||||
|
ScrapedLink(
|
||||||
|
url=normalized_href,
|
||||||
|
title="",
|
||||||
|
text=text[:200],
|
||||||
|
source_url=normalized_url,
|
||||||
|
bucket="program",
|
||||||
|
is_profile_page=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for faculty links
|
||||||
|
if matches_any(text_lower, FACULTY_KEYWORDS) or matches_any(href_lower, FACULTY_KEYWORDS):
|
||||||
|
found_urls.add(normalized_href)
|
||||||
|
results.append(
|
||||||
|
ScrapedLink(
|
||||||
|
url=normalized_href,
|
||||||
|
title="",
|
||||||
|
text=text[:200],
|
||||||
|
source_url=normalized_url,
|
||||||
|
bucket="faculty",
|
||||||
|
is_profile_page=is_profile,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Queue for further crawling
|
||||||
|
if depth < settings.max_depth and is_same_domain(settings.root_url, normalized_href):
|
||||||
|
# Prioritize directory pages
|
||||||
|
link_priority = 0 if is_directory_url(normalized_href) else 1
|
||||||
|
queue.append((link_priority, normalized_href, depth + 1))
|
||||||
|
|
||||||
|
await page.close()
|
||||||
|
|
||||||
|
# Polite delay between requests
|
||||||
|
await asyncio.sleep(settings.request_delay)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
await context.close()
|
||||||
|
await browser.close()
|
||||||
|
|
||||||
|
# Verify links if enabled
|
||||||
|
if settings.verify_links and results:
|
||||||
|
print(f"\nVerifying {len(results)} links...")
|
||||||
|
browser = await browser_launcher.launch(headless=True)
|
||||||
|
context = await browser.new_context()
|
||||||
|
|
||||||
|
verified_results = []
|
||||||
|
for i, link in enumerate(results):
|
||||||
|
if link.url in [r.url for r in verified_results]:
|
||||||
|
continue # Skip duplicates
|
||||||
|
|
||||||
|
print(f" [{i+1}/{len(results)}] Verifying: {link.url[:60]}...")
|
||||||
|
is_valid, status, title = await verify_link(context, link.url)
|
||||||
|
link.is_verified = True
|
||||||
|
link.http_status = status
|
||||||
|
link.title = title or link.text
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
verified_results.append(link)
|
||||||
|
else:
|
||||||
|
print(f" Invalid (HTTP {status})")
|
||||||
|
|
||||||
|
await asyncio.sleep(0.5) # Delay between verifications
|
||||||
|
|
||||||
|
await context.close()
|
||||||
|
await browser.close()
|
||||||
|
results = verified_results
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def deduplicate_results(results: List[ScrapedLink]) -> List[ScrapedLink]:
|
||||||
|
"""Remove duplicate URLs, keeping the first occurrence."""
|
||||||
|
seen: Set[str] = set()
|
||||||
|
unique = []
|
||||||
|
for link in results:
|
||||||
|
if link.url not in seen:
|
||||||
|
seen.add(link.url)
|
||||||
|
unique.append(link)
|
||||||
|
return unique
|
||||||
|
|
||||||
|
|
||||||
|
def serialize(results: List[ScrapedLink], target: Path, root_url: str) -> None:
|
||||||
|
"""Save results to JSON file with statistics."""
|
||||||
|
results = deduplicate_results(results)
|
||||||
|
|
||||||
|
program_links = [link for link in results if link.bucket == "program"]
|
||||||
|
faculty_links = [link for link in results if link.bucket == "faculty"]
|
||||||
|
profile_pages = [link for link in faculty_links if link.is_profile_page]
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"root_url": root_url,
|
||||||
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"statistics": {
|
||||||
|
"total_links": len(results),
|
||||||
|
"program_links": len(program_links),
|
||||||
|
"faculty_links": len(faculty_links),
|
||||||
|
"profile_pages": len(profile_pages),
|
||||||
|
"verified_links": len([r for r in results if r.is_verified and r.http_status == 200]),
|
||||||
|
},
|
||||||
|
"program_links": [asdict(link) for link in program_links],
|
||||||
|
"faculty_links": [asdict(link) for link in faculty_links],
|
||||||
|
"notes": EXTRA_NOTES,
|
||||||
|
"metadata_fields": METADATA_FIELDS,
|
||||||
|
}
|
||||||
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
target.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
|
print(f"\nResults saved to: {target}")
|
||||||
|
print(f" Total links: {len(results)}")
|
||||||
|
print(f" Program links: {len(program_links)}")
|
||||||
|
print(f" Faculty links: {len(faculty_links)}")
|
||||||
|
print(f" Profile pages: {len(profile_pages)}")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Playwright scraper generated by the Agno agent for {{ request.target_url }}."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--root-url",
|
||||||
|
default="{{ request.target_url }}",
|
||||||
|
help="Seed url to start crawling from.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-depth",
|
||||||
|
type=int,
|
||||||
|
default={{ request.max_depth }},
|
||||||
|
help="Maximum crawl depth.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-pages",
|
||||||
|
type=int,
|
||||||
|
default={{ request.max_pages }},
|
||||||
|
help="Maximum number of pages to visit.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
default=Path("{{ plan.project_slug }}_results.json"),
|
||||||
|
help="Where to save the JSON output.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--headless",
|
||||||
|
action="store_true",
|
||||||
|
default=True,
|
||||||
|
help="Run browser in headless mode (default: True).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-headless",
|
||||||
|
action="store_false",
|
||||||
|
dest="headless",
|
||||||
|
help="Run browser with visible window.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--browser",
|
||||||
|
choices=["chromium", "firefox", "webkit"],
|
||||||
|
default="chromium",
|
||||||
|
help="Browser engine to launch via Playwright.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-verify",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Skip link verification step.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--delay",
|
||||||
|
type=float,
|
||||||
|
default=1.0,
|
||||||
|
help="Delay between requests in seconds (polite crawling).",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
async def main_async() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
settings = ScrapeSettings(
|
||||||
|
root_url=args.root_url,
|
||||||
|
max_depth=args.max_depth,
|
||||||
|
max_pages=args.max_pages,
|
||||||
|
headless=args.headless,
|
||||||
|
output=args.output,
|
||||||
|
verify_links=not args.no_verify,
|
||||||
|
request_delay=args.delay,
|
||||||
|
)
|
||||||
|
links = await crawl(settings, browser_name=args.browser)
|
||||||
|
serialize(links, settings.output, settings.root_url)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
asyncio.run(main_async())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
20
src/university_agent/writer.py
Normal file
20
src/university_agent/writer.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .config import Settings
|
||||||
|
from .models import ScriptPlan
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptWriter:
|
||||||
|
"""Persist generated scripts to disk."""
|
||||||
|
|
||||||
|
def __init__(self, settings: Settings):
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
def write(self, plan: ScriptPlan, script_body: str) -> Path:
|
||||||
|
filename = plan.script_name or self.settings.default_script_name
|
||||||
|
destination = self.settings.output_dir / filename
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
destination.write_text(script_body, encoding="utf-8")
|
||||||
|
return destination
|
||||||
689
stanford-masters-faculty_masters.json
Normal file
689
stanford-masters-faculty_masters.json
Normal file
@ -0,0 +1,689 @@
|
|||||||
|
{
|
||||||
|
"root_url": "https://www.stanford.edu",
|
||||||
|
"program_links": [
|
||||||
|
{
|
||||||
|
"url": "http://facts.stanford.edu/academics/undergraduate",
|
||||||
|
"text": "Facts about the Undergraduate Program",
|
||||||
|
"source_url": "https://www.stanford.edu/academics/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://facts.stanford.edu/academics/graduate-profile",
|
||||||
|
"text": "Facts about Graduate Studies",
|
||||||
|
"source_url": "https://www.stanford.edu/academics/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/directors-graduate-studies",
|
||||||
|
"text": "Directors of Graduate Studies",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://humsci.stanford.edu/prospective-students/guide-getting-grad-school/funding-graduate-studies",
|
||||||
|
"text": "Funding Graduate Studies",
|
||||||
|
"source_url": "http://humsci.stanford.edu/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/students-academics/student-success-and-engagement/undergraduate-programs",
|
||||||
|
"text": "Undergraduate Programs",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/prospective-graduate-programs",
|
||||||
|
"text": "Prospective Graduate Programs",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/students-academics/student-success-and-engagement/graduate-programs",
|
||||||
|
"text": "Graduate Programs",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "program"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/students-academics/student-success-and-engagement/funding-and-financial-aid/funding-your-masters",
|
||||||
|
"text": "Funding Your Master’s Degree",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "program"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"faculty_links": [
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/student-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/student-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/student-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Skip to content",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://facultyaffairs.stanford.edu/",
|
||||||
|
"text": "Faculty Affairs",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://facultysenate.stanford.edu/",
|
||||||
|
"text": "Faculty Senate",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://fsh.stanford.edu/",
|
||||||
|
"text": "Faculty Housing",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://facultydevelopment.stanford.edu/",
|
||||||
|
"text": "Faculty Development & Engagement",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://fingate.stanford.edu/faculty/",
|
||||||
|
"text": "Faculty Financial Activities",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://facultyclub.stanford.edu/",
|
||||||
|
"text": "Faculty Club",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://helpcenter.stanford.edu/",
|
||||||
|
"text": "Faculty/Staff Help Center",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://fingate.stanford.edu/faculty/",
|
||||||
|
"text": "Faculty Financial Activities",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/academics/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/academics/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/academics/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/research/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/research/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/research/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/health-care/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/health-care/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/health-care/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/campus-life/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/campus-life/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/campus-life/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.stanford.edu/faculty-staff-gateway/",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://www.stanford.edu/about/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://facts.stanford.edu/academics/faculty/",
|
||||||
|
"text": "Pulitzer Prizes, National Medals, and MacArthur Fellows",
|
||||||
|
"source_url": "https://www.stanford.edu/about/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stanfordwho.stanford.edu/",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "https://www.stanford.edu/about/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openfacultypositions.stanford.edu/",
|
||||||
|
"text": "Faculty Positions",
|
||||||
|
"source_url": "https://www.stanford.edu/about/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://undergrad.stanford.edu/programs/bhc/faculty",
|
||||||
|
"text": "For Faculty",
|
||||||
|
"source_url": "https://undergrad.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpuefacstaff.sites.stanford.edu/",
|
||||||
|
"text": "For Faculty / Staff",
|
||||||
|
"source_url": "https://undergrad.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpuefacstaff.stanford.edu/",
|
||||||
|
"text": "For Faculty / Staff",
|
||||||
|
"source_url": "https://undergrad.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff",
|
||||||
|
"text": "Guidance for Faculty & Staff",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/advising-initiative",
|
||||||
|
"text": "Advising Initiative",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/advising-initiative/programs-faculty",
|
||||||
|
"text": "Programs for Faculty",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/advising-initiative/promising-practices-stanford",
|
||||||
|
"text": "Promising Practices at Stanford",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/advising-initiative/resources-stanford-beyond",
|
||||||
|
"text": "Resources at Stanford & Beyond",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/vpge-fellowship-policies",
|
||||||
|
"text": "VPGE Fellowship Policies",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/vpge-fellowship-policies/gfs-entry-information",
|
||||||
|
"text": "GFS Entry Information",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/directors-graduate-studies",
|
||||||
|
"text": "Directors of Graduate Studies",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/graduate-student-data",
|
||||||
|
"text": "Graduate Student Data",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/vpge-fellowship-policies",
|
||||||
|
"text": "VPGE Fellowship Policies",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/guidance-faculty-staff/vpge-fellowship-policies/gfs-entry-information",
|
||||||
|
"text": "GFS Entry Information",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/fellowships-funding/current-vpge-fellows/graduate-fellowships-faculty-advisory-committee",
|
||||||
|
"text": "Graduate Fellowships Faculty Advisory Committee",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/about-vpge/faculty-advisory-committee",
|
||||||
|
"text": "Faculty Advisory Committee",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/about-vpge/student-postdoc-advisory-committee",
|
||||||
|
"text": "Student & Postdoc Advisory Committee",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/information-faculty-staff",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://vpge.stanford.edu/gateways/information-faculty-staff",
|
||||||
|
"text": "Faculty & Staff",
|
||||||
|
"source_url": "https://vpge.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://humsci.stanford.edu/prospective-students/guide-getting-grad-school/interviewing-and-talking-prospective-faculty",
|
||||||
|
"text": "Interviewing and Talking with Prospective Faculty",
|
||||||
|
"source_url": "http://humsci.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://humsci.stanford.edu/sites/default/files/styles/fixed_height_2x/public/history_itm.png?itok=YBm3Ma2D",
|
||||||
|
"text": "View Professor Caroline Winterer discusses an archival book with students",
|
||||||
|
"source_url": "http://humsci.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://humsci.stanford.edu/sites/default/files/styles/fixed_height_2x/public/1-2025-white-plaza-observation-3.jpg?itok=6QlEgl-7",
|
||||||
|
"text": "View A photo of first year student Serena Young as she sits inside the White Plaza pavilion, observing people",
|
||||||
|
"source_url": "http://humsci.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/students-academics/student-success-and-engagement/graduate-programs/deans-graduate-student",
|
||||||
|
"text": "Dean’s Graduate Student Advisory Council",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research",
|
||||||
|
"text": "Faculty & Research",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty",
|
||||||
|
"text": "Faculty",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty/emeritus",
|
||||||
|
"text": "Emeritus Faculty",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty/memoriam",
|
||||||
|
"text": "In Memoriam",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments",
|
||||||
|
"text": "Departments",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/aeronautics-astronautics",
|
||||||
|
"text": "Aeronautics & Astronautics",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/bioengineering",
|
||||||
|
"text": "Bioengineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/chemical-engineering",
|
||||||
|
"text": "Chemical Engineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/civil-environmental-engineering",
|
||||||
|
"text": "Civil & Environmental Engineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/computer-science",
|
||||||
|
"text": "Computer Science",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/electrical-engineering",
|
||||||
|
"text": "Electrical Engineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/management-science-engineering",
|
||||||
|
"text": "Management Science & Engineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/materials-science-engineering",
|
||||||
|
"text": "Materials Science & Engineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments/mechanical-engineering",
|
||||||
|
"text": "Mechanical Engineering",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/institutes-labs-and-centers",
|
||||||
|
"text": "Institutes, Labs and Centers",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards",
|
||||||
|
"text": "Faculty Awards",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2025-26",
|
||||||
|
"text": "Faculty Awards 2025-2026",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2024-25",
|
||||||
|
"text": "Faculty Awards 2024-2025",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2023-24",
|
||||||
|
"text": "Faculty Awards 2023-2024",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2022-2023",
|
||||||
|
"text": "Faculty Awards 2022-2023",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2021-2022",
|
||||||
|
"text": "Faculty Awards 2021-2022",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2020-2021",
|
||||||
|
"text": "Faculty Awards 2020-2021",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2019-2020",
|
||||||
|
"text": "Faculty Awards 2019-2020",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2018-2019",
|
||||||
|
"text": "Faculty Awards 2018-2019",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2016-2017",
|
||||||
|
"text": "Faculty Awards 2016-2017",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2015-2016",
|
||||||
|
"text": "Faculty Awards 2015-2016",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2014-2015",
|
||||||
|
"text": "Faculty Awards 2014-2015",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2012-2013",
|
||||||
|
"text": "Faculty Awards 2012-2013",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2011-2012",
|
||||||
|
"text": "Faculty Awards 2011-2012",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2010-2011",
|
||||||
|
"text": "Faculty Awards 2010-2011",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2009-2010",
|
||||||
|
"text": "Faculty Awards 2009-2010",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2008-2009",
|
||||||
|
"text": "Faculty Awards 2008-2009",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2007-2008",
|
||||||
|
"text": "Faculty Awards 2007-2008",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2006-2007",
|
||||||
|
"text": "Faculty Awards 2006-2007",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty-awards/faculty-awards-2005-2006",
|
||||||
|
"text": "Faculty Awards 2005-2006",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/get-involved/support-engineering/funding-initiatives/endowed-professorships",
|
||||||
|
"text": "Endowed Professorships",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/get-involved/support-engineering/funding-initiatives/endowed-faculty-scholars",
|
||||||
|
"text": "Endowed Faculty Scholars",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/get-involved/support-engineering/funding-initiatives/faculty-launch-fund",
|
||||||
|
"text": "Faculty Launch Fund",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/about/deans-office/deans-office-directory",
|
||||||
|
"text": "Directory",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research",
|
||||||
|
"text": "research",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/faculty",
|
||||||
|
"text": "faculty",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/faculty-research/departments",
|
||||||
|
"text": "Departments",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://engineering.stanford.edu/open-faculty-positions",
|
||||||
|
"text": "Open Faculty Positions",
|
||||||
|
"source_url": "http://engineering.stanford.edu/",
|
||||||
|
"bucket": "faculty"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
"Stanford's seven schools each host their own subdomains (e.g., engineering.stanford.edu, law.stanford.edu); the script should allow cross-subdomain crawling within *.stanford.edu while still counting against the page cap.",
|
||||||
|
"Many program pages load additional content via JavaScript; use page.wait_for_load_state('networkidle') or explicit waits before extracting links.",
|
||||||
|
"Faculty directories may paginate or use infinite scroll; handle at least the first visible batch given the tight page cap.",
|
||||||
|
"Respect polite crawling: insert a 1-2 second delay between requests to avoid rate-limiting."
|
||||||
|
],
|
||||||
|
"metadata_fields": [
|
||||||
|
"url",
|
||||||
|
"title",
|
||||||
|
"entity_type",
|
||||||
|
"department",
|
||||||
|
"school",
|
||||||
|
"description",
|
||||||
|
"contact_email",
|
||||||
|
"scraped_at"
|
||||||
|
]
|
||||||
|
}
|
||||||
48
test_run.py
Normal file
48
test_run.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Quick test script to run the agent."""
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Load environment variables from User scope
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
def get_user_env(name):
|
||||||
|
"""Get user environment variable on Windows."""
|
||||||
|
try:
|
||||||
|
import winreg
|
||||||
|
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Environment") as key:
|
||||||
|
return winreg.QueryValueEx(key, name)[0]
|
||||||
|
except Exception:
|
||||||
|
return os.environ.get(name)
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
api_key = get_user_env("ANTHROPIC_API_KEY")
|
||||||
|
base_url = get_user_env("ANTHROPIC_BASE_URL")
|
||||||
|
|
||||||
|
if api_key:
|
||||||
|
os.environ["ANTHROPIC_API_KEY"] = api_key
|
||||||
|
if base_url:
|
||||||
|
os.environ["ANTHROPIC_BASE_URL"] = base_url
|
||||||
|
|
||||||
|
print(f"API Key set: {bool(api_key)}")
|
||||||
|
print(f"Base URL: {base_url}")
|
||||||
|
|
||||||
|
# Run the agent
|
||||||
|
from university_agent import GenerationEngine, GenerationRequest, Settings
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
|
print(f"Using model: {settings.anthropic_model}")
|
||||||
|
|
||||||
|
engine = GenerationEngine(settings)
|
||||||
|
request = GenerationRequest(
|
||||||
|
target_url="https://www.stanford.edu",
|
||||||
|
campus_name="Stanford",
|
||||||
|
assumed_language="English",
|
||||||
|
max_depth=3, # Increased depth to reach individual profiles
|
||||||
|
max_pages=30, # More pages to explore
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Starting generation...")
|
||||||
|
result = engine.generate(request, capture_snapshot=True)
|
||||||
|
print(f"Script saved to: {result.script_path}")
|
||||||
|
print(f"Project slug: {result.plan.project_slug}")
|
||||||
762
uv.lock
generated
Normal file
762
uv.lock
generated
Normal file
@ -0,0 +1,762 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agno"
|
||||||
|
version = "2.3.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "docstring-parser" },
|
||||||
|
{ name = "gitpython" },
|
||||||
|
{ name = "h11" },
|
||||||
|
{ name = "httpx", extra = ["http2"] },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "python-multipart" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "typer" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/af/7d/6593d250d415407e45e0cade97e78a708ed76f77ed5457ab33aee64760ab/agno-2.3.8.tar.gz", hash = "sha256:212a4e14f51bea035922522cf6950ac93ba76018ef394d3790cd418dbe257500", size = 1211067, upload-time = "2025-12-05T15:28:55.947Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/48/87d574a1bdf5832b5c279fd5284c54d9c9fabe62e60a71f0db98c929f546/agno-2.3.8-py3-none-any.whl", hash = "sha256:a7a2f29dda8880cfbb25475feec601b61d37e8b186df35de77b90429f7bc55b9", size = 1471035, upload-time = "2025-12-05T15:28:54.279Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.12.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2025.11.12"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "docstring-parser"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitdb"
|
||||||
|
version = "4.0.12"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "smmap" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitpython"
|
||||||
|
version = "3.1.45"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "gitdb" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "greenlet"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "hpack" },
|
||||||
|
{ name = "hyperframe" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hpack"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "h11" },
|
||||||
|
]
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "httpcore" },
|
||||||
|
{ name = "idna" },
|
||||||
|
]
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
http2 = [
|
||||||
|
{ name = "h2" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyperframe"
|
||||||
|
version = "6.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.11"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markupsafe" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mdurl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "playwright"
|
||||||
|
version = "1.56.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "greenlet" },
|
||||||
|
{ name = "pyee" },
|
||||||
|
]
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/31/a5362cee43f844509f1f10d8a27c9cc0e2f7bdce5353d304d93b2151c1b1/playwright-1.56.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:b33eb89c516cbc6723f2e3523bada4a4eb0984a9c411325c02d7016a5d625e9c", size = 40611424, upload-time = "2025-11-11T18:39:10.175Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/95/347eef596d8778fb53590dc326c344d427fa19ba3d42b646fce2a4572eb3/playwright-1.56.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b228b3395212b9472a4ee5f1afe40d376eef9568eb039fcb3e563de8f4f4657b", size = 39400228, upload-time = "2025-11-11T18:39:13.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/54/6ad97b08b2ca1dfcb4fbde4536c4f45c0d9d8b1857a2d20e7bbfdf43bf15/playwright-1.56.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:0ef7e6fd653267798a8a968ff7aa2dcac14398b7dd7440ef57524e01e0fbbd65", size = 40611424, upload-time = "2025-11-11T18:39:17.093Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/76/6d409e37e82cdd5dda3df1ab958130ae32b46e42458bd4fc93d7eb8749cb/playwright-1.56.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:404be089b49d94bc4c1fe0dfb07664bda5ffe87789034a03bffb884489bdfb5c", size = 46263122, upload-time = "2025-11-11T18:39:20.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/84/fb292cc5d45f3252e255ea39066cd1d2385c61c6c1596548dfbf59c88605/playwright-1.56.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64cda7cf4e51c0d35dab55190841bfcdfb5871685ec22cb722cd0ad2df183e34", size = 46110645, upload-time = "2025-11-11T18:39:24.005Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/bd/8c02c3388ae14edc374ac9f22cbe4e14826c6a51b2d8eaf86e89fabee264/playwright-1.56.0-py3-none-win32.whl", hash = "sha256:d87b79bcb082092d916a332c27ec9732e0418c319755d235d93cc6be13bdd721", size = 35639837, upload-time = "2025-11-11T18:39:27.174Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/27/f13b538fbc6b7a00152f4379054a49f6abc0bf55ac86f677ae54bc49fb82/playwright-1.56.0-py3-none-win_amd64.whl", hash = "sha256:3c7fc49bb9e673489bf2622855f9486d41c5101bbed964638552b864c4591f94", size = 35639843, upload-time = "2025-11-11T18:39:30.851Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/c7/3ee8b556107995846576b4fe42a08ed49b8677619421f2afacf6ee421138/playwright-1.56.0-py3-none-win_arm64.whl", hash = "sha256:2745490ae8dd58d27e5ea4d9aa28402e8e2991eb84fb4b2fd5fbde2106716f6f", size = 31248959, upload-time = "2025-11-11T18:39:33.998Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.12.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.41.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-settings"
|
||||||
|
version = "2.12.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyee"
|
||||||
|
version = "13.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-multipart"
|
||||||
|
version = "0.0.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "14.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markdown-it-py" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.14.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smmap"
|
||||||
|
version = "5.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ty"
|
||||||
|
version = "0.0.1a32"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/26/92/8da015685fb83734a2a83de02080e64d182509de77fa9bcf3eed12eeab4b/ty-0.0.1a32.tar.gz", hash = "sha256:12f62e8a3dd0eaeb9557d74b1c32f0616ae40eae10a4f411e1e2a73225f67ff2", size = 4689151, upload-time = "2025-12-05T21:04:26.885Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/e6/fdc35c9ba047f16afdfedf36fb51c221e0190ccde9f70ee28e77084d6612/ty-0.0.1a32-py3-none-linux_armv6l.whl", hash = "sha256:ffe595eaf616f06f58f951766477830a55c2502d2c9f77dde8f60d9a836e0645", size = 9673128, upload-time = "2025-12-05T21:04:17.702Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/20/eaff31048e2f309f37478f7d715c8de9f9bab03cba4758da27b9311147af/ty-0.0.1a32-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:07f1dce88ad6028fb14665aefe4e6697012c34bd48edd37d02b7eb6a833dbf62", size = 9434094, upload-time = "2025-12-05T21:04:03.383Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/d4/ea8ed57d11b81c459f23561fd6bfb0f54a8d4120cf72541e3bdf71d46202/ty-0.0.1a32-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8fab7ed12528c77ddd600a9638ca859156a53c20f1e381353fa87a255bd397eb", size = 8980296, upload-time = "2025-12-05T21:04:28.912Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/02/3ce98bbfbb3916678d717ee69358d38a404ca9a39391dda8874b66dd5ee7/ty-0.0.1a32-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace395280fc21e25eff0a53cfbd68170f90a4b8ef2f85dfabe1ecbca2ced456b", size = 9263054, upload-time = "2025-12-05T21:04:05.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/be/a639638bcd1664de2d70a87da6c4fe0e3272a60b7fa3f0c108a956a456bd/ty-0.0.1a32-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bcbeed7f5ed8e3c1c7e525fce541e7b943ac04ee7fe369a926551b5e50ea4a8", size = 9451396, upload-time = "2025-12-05T21:04:01.265Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/a4/2bcf54e842a3d10dc14b369f28a3bab530c5d7ddba624e910b212bda93ee/ty-0.0.1a32-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60ff2e4493f90f81a260205d87719bb1d3420928a1e4a2a7454af7cbdfed2047", size = 9862726, upload-time = "2025-12-05T21:04:08.806Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/c7/19e6719496e59f2f082f34bcac312698366cf50879fdcc3ef76298bfe6a0/ty-0.0.1a32-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:53cad50a59a0d943b06872e0b10f9f2b564805c2ea93f64c7798852bc1901954", size = 10475051, upload-time = "2025-12-05T21:04:31.059Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/77/bdf0ddb066d2b62f141d058f8a33bb7c8628cdbb8bfa75b20e296b79fb4e/ty-0.0.1a32-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:343d43cdc1d7f649ea2baa64ac2b479da3d679239b94509f1df12f7211561ea9", size = 10232712, upload-time = "2025-12-05T21:04:19.849Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/07/f73260a461762a581a007015c1019d40658828ce41576f8c1db88dee574d/ty-0.0.1a32-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f45483e4a84bcf622413712164ea687ce323a9f7013b9e7977c5d623ed937ca9", size = 10237705, upload-time = "2025-12-05T21:04:35.366Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/57/dbb92206cf2f798d8c51ea16504e8afb90a139d0ff105c31cec9a1db29f9/ty-0.0.1a32-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d452f30d47002a6bafc36d1b6aee42c321e9ec9f7f43a04a2ee7d48c208b86c", size = 9766469, upload-time = "2025-12-05T21:04:22.236Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/5e/143d93bd143abcebcbaa98c8aeec78898553d62d0a5a432cd79e0cf5bd6d/ty-0.0.1a32-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:86c4e31737fe954637890cef1f3e1b479ffb20e836cac3b76050bdbe80005010", size = 9238592, upload-time = "2025-12-05T21:04:11.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/b8/225230ae097ed88f3c92ad974dd77f8e4f86f2594d9cd0c729da39769878/ty-0.0.1a32-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:daf15fa03bc39a76a0fbc9c2d81d79d528f584e3fbe08d71981e3f7912db91d6", size = 9502161, upload-time = "2025-12-05T21:04:37.642Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/13/cc89955c9637f25f3aca2dd7749c6008639ef036f0b9bea3e9d89e892ff9/ty-0.0.1a32-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6128f6bab5c6dab3d08689fed1d529dc34f50f221f89c8e16064ed0c549dad7a", size = 9603058, upload-time = "2025-12-05T21:04:39.532Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/77/1fe2793c8065a02d1f70ca7da1b87db49ca621bcbbdb79a18ad79d5d0ab2/ty-0.0.1a32-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:55aab688be1b46776a5a458a1993cae0da7725932c45393399c479c2fa979337", size = 9879903, upload-time = "2025-12-05T21:04:13.567Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/47/fd58e80a3e42310b4b649340d5d97403fe796146cae8678b3a031a414b8e/ty-0.0.1a32-py3-none-win32.whl", hash = "sha256:f55ec25088a09236ad1578b656a07fa009c3a353f5923486905ba48175d142a6", size = 9077703, upload-time = "2025-12-05T21:04:15.849Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/96/209c417c69317339ea8e9b3277fd98364a0e97dd1ffd3585e143ec7b4e57/ty-0.0.1a32-py3-none-win_amd64.whl", hash = "sha256:ed8d5cbd4e47dfed86aaa27e243008aa4e82b6a5434f3ab95c26d3ee5874d9d7", size = 9922426, upload-time = "2025-12-05T21:04:33.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/1c/350fd851fb91244f8c80cec218009cbee7564d76c14e2f423b47e69a5cbc/ty-0.0.1a32-py3-none-win_arm64.whl", hash = "sha256:dbb25f9b513d34cee8ce419514eaef03313f45c3f7ab4eb6e6d427ea1f6854af", size = 9453761, upload-time = "2025-12-05T21:04:24.502Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typer"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "shellingham" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
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" }
|
||||||
|
wheels = [
|
||||||
|
{ 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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "university-agent"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "agno" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "jinja2" },
|
||||||
|
{ name = "playwright" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "typer" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "ty" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
{ name = "ty" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "agno", specifier = ">=2.3.8" },
|
||||||
|
{ name = "httpx", specifier = ">=0.28" },
|
||||||
|
{ name = "jinja2", specifier = ">=3.1" },
|
||||||
|
{ name = "playwright", specifier = ">=1.48" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.9" },
|
||||||
|
{ name = "pydantic-settings", specifier = ">=2.6" },
|
||||||
|
{ name = "rich", specifier = ">=13.7" },
|
||||||
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6.9" },
|
||||||
|
{ name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.1a32" },
|
||||||
|
{ name = "typer", specifier = ">=0.12.5" },
|
||||||
|
]
|
||||||
|
provides-extras = ["dev"]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "ruff", specifier = ">=0.6.9" },
|
||||||
|
{ name = "ty", specifier = ">=0.0.1a32" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user