stage22: Abschluss & Production Readiness — E2E-Tests, CHANGELOG, Dokumentation

This commit is contained in:
NSCT Agent
2026-09-05 15:03:48 +00:00
parent 8ea6269f9a
commit 9ae1a7ba21
9 changed files with 1550 additions and 89 deletions

301
API.md
View File

@@ -1,8 +1,6 @@
# NSCT — API-Referenz # NSCT — API-Referenz
> **Hinweis:** Diese API-Referenz beschreibt nur die Endpunkte > **Hinweis:** Diese API-Referenz beschreibt den aktuellen Stand (Stage 22).
> von Stage 0. Die eigentliche Evidence-Pipeline (Suche,
> Analyse, Report) wird in späteren Stages implementiert.
## Server-Adresse ## Server-Adresse
@@ -54,7 +52,7 @@ Wenn bereit:
"status": "ready", "status": "ready",
"llm": "ok", "llm": "ok",
"database": "unknown", "database": "unknown",
"llm_model": "Qwen3.6-6-35B" "llm_model": "Qwen3.6-35B"
} }
``` ```
@@ -71,10 +69,6 @@ Wenn nicht bereit:
- `ready` — Alle geprüften Services sind erreichbar - `ready` — Alle geprüften Services sind erreichbar
- `not_ready` — Mindestens ein Service ist nicht erreichbar - `not_ready` — Mindestens ein Service ist nicht erreichbar
**Mögliche `llm`-Werte:**
- `ok` — LLM-Provider ist erreichbar
- `error:<detail>` — Fehlerbeschreibung (HTTP-Statuscode oder Exception-Typ)
--- ---
### GET /providers ### GET /providers
@@ -91,12 +85,12 @@ GET /providers
{ {
"llm": { "llm": {
"available": true, "available": true,
"model": "Qwen3.6-6-35B", "model": "Qwen3.6-35B",
"max_concurrency": 3 "max_concurrency": 3
}, },
"vision": { "vision": {
"available": true, "available": true,
"model": "Qwen2-VL-3B" "model": "Qwen2.5-VL-3B"
}, },
"audio": { "audio": {
"available": true, "available": true,
@@ -115,28 +109,291 @@ GET /providers
``` ```
**Wichtig:** Dieser Endpoint gibt **keinerlei** Secrets, API-Keys **Wichtig:** Dieser Endpoint gibt **keinerlei** Secrets, API-Keys
oder senssible Konfigurationswerte zurück. oder sensible Konfigurationswerte zurück.
--- ---
## Entwicklung (nur mit NSCT_DEBUG=true) ## Research API (Stage 14+)
### GET /docs Alle Research-Endpoints haben das Präfix `/v1/research`.
Swagger UI mit interaktiver API-Documentation. ---
```bash ### POST /v1/research
curl http://localhost:8080/docs
**Beschreibung:** Startet eine neue Recherche asynchron (Background-Pipeline).
Die Antwort kommt sofort — das eigentliche Ergebnis ist über
`GET /v1/research/{id}/report` abrufbar.
**Request:**
```json
{
"query": "Welche wesentlichen Fortschritte gab es im letzten Jahr bei Kernfusion?",
"language": "de",
"depth": "normal",
"extra_queries": ["Fusion breakthroughs 2025", "commercial fusion progress"]
}
``` ```
### GET /redoc **Parameter:**
ReDoc-Generierung. | Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `query` | string | yes | — | Forschungsfrage (12000 Zeichen) |
| `language` | string | no | `"de"` | Sprachcode (z.B. `"de"`, `"en"`) |
| `depth` | string | no | `"normal"` | Suchtiefe: `"quick"`, `"normal"`, `"deep"` |
| `extra_queries` | string[] | no | `null` | Optionale zusätzliche Suchanfragen |
```bash **Depth-Konfiguration:**
curl http://localhost:8080/redoc
| Tiefe | max_search_queries | max_sources | max_llm_requests | max_duration | max_context |
|-------|--------------------|-------------|-------------------|--------------|-------------|
| `quick` | 10 | 15 | 15 | 120s | 16k |
| `normal` | 20 | 30 | 30 | 300s | 24k |
| `deep` | 40 | 60 | 60 | 600s | 32k |
**Response (200 OK):**
```json
{
"research_id": "uuid-...",
"status": "pending",
"query": "Welche Fortschritte bei Kernfusion?",
"depth": "normal",
"state": "created"
}
``` ```
**Error (400):**
```json
{
"detail": "Ungültige Tiefe 'ultra'. Gültig: 'quick', 'normal', 'deep'."
}
```
---
### GET /v1/research
**Beschreibung:** Liste aller Research-Runs (paginiert).
**Query-Parameter:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | int | 10 | Max Anzahl Ergebnisse |
| `offset` | int | 0 | Offset für Pagination |
**Response (200 OK):**
```json
{
"total": 5,
"limit": 10,
"offset": 0,
"items": [
{
"research_id": "uuid-...",
"query": "Kernfusion",
"depth": "normal",
"state": "completed",
"created_at": "2025-01-01T00:00:00+00:00",
"source_count": 5,
"claim_count": 12
}
]
}
```
---
### GET /v1/research/{id}
**Beschreibung:** Metadaten eines Research-Runs.
**Path-Parameter:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | UUID des Research-Runs |
**Response (200 OK):**
```json
{
"research_id": "uuid-...",
"query": "Kernfusion 2025",
"depth": "normal",
"state": "completed",
"created_at": "2025-01-01T00:00:00+00:00",
"updated_at": "2025-01-01T00:05:00+00:00",
"search_count": 20,
"source_count": 5,
"claim_count": 12,
"is_completed": true,
"is_running": false
}
```
---
### GET /v1/research/{id}/status
**Beschreibung:** State-Machine Status eines Research-Runs.
Identisch zu `GET /v1/research/{id}` — gleicher Response.
---
### GET /v1/research/{id}/sources
**Beschreibung:** Quellen für einen Research-Run.
**Response (200 OK):**
```json
{
"research_id": "uuid-...",
"sources": [
{
"id": "s1",
"url": "https://example.com/1",
"title": "Beispiel Quelle",
"domain": "example.com",
"source_type": "primary",
"retrieved_at": "2025-01-01T00:01:00+00:00"
}
],
"total": 1
}
```
---
### GET /v1/research/{id}/claims
**Beschreibung:** Claims (Behauptungen) für einen Research-Run.
**Response (200 OK):**
```json
{
"research_id": "uuid-...",
"claims": [
{
"id": "c1",
"research_run_id": "uuid-...",
"source_id": "s1",
"claim_text": "Behauptung A",
"evidence_span": "Quelle 1, Abschnitt 2",
"claim_type": "factual",
"confidence": 0.85
}
],
"total": 1
}
```
---
### GET /v1/research/{id}/evidence
**Beschreibung:** Evidence-Scores (6-dimensional) für einen Research-Run.
**Response (200 OK):**
```json
{
"research_id": "uuid-...",
"evidence": [
{
"claim_id": "c1",
"research_run_id": "uuid-...",
"evidence_type": "primary_report",
"source_independence_score": 0.9,
"primary_source_proximity": 0.95,
"cross_source_support": 0.7,
"contradiction_level": 0.2,
"evidence_directness": 0.88,
"date_relevance_score": 0.95
}
],
"total": 1
}
```
---
### GET /v1/research/{id}/report
**Beschreibung:** Synthese-Bericht (finaler Research Report).
**Response (200 OK):**
```json
{
"research_id": "uuid-...",
"query": "Kernfusion 2025",
"summary": "Zusammenfassung der Recherche...",
"findings": [
{
"text": "Fundamentale Erkenntnis A",
"confidence": 0.85,
"source_ids": ["s1", "s2"]
}
],
"disagreements": [
{
"claim": "Behauptung X",
"positions": [
{"source": "Quelle 1", "view": "Für X"},
{"source": "Quelle 2", "view": "Gegen X"}
]
}
],
"uncertainties": ["Nicht ausreichend belegte Behauptung Y"],
"source_statistics": {
"total_sources": 5,
"primary_sources": 2,
"secondary_sources": 3
},
"methodology": "NSCT Evidence Pipeline — Search, Extract, Claim, Compare, Synthesize",
"generated_at": "2025-01-01T00:00:00+00:00"
}
```
---
### DELETE /v1/research/{id}
**Beschreibung:** Löscht einen Research-Run (nur wenn nicht completed).
**Response (200 OK):**
```json
{
"status": "deleted",
"research_id": "uuid-..."
}
```
**Error (400):** Completed runs sind immutable.
---
## Prometheus Metrics
### GET /metrics
**Response:** Prometheus Text Format mit allen Metriken:
- `search_queries_total`
- `sources_discovered_total`
- `sources_fetched_total`
- `sources_rejected_total`
- `claims_extracted_total`
- `duplicate_sources_total`
- `contradictions_detected_total`
- `llm_requests_total`
- `llm_tokens_input_total`
- `llm_tokens_output_total`
- `research_duration_seconds`
- `research_completed_total`
- `research_failed_total`
- `active_research_runs`
--- ---
## Error Response Format ## Error Response Format
@@ -153,5 +410,5 @@ Alle Fehler folgen einem konsistenten Format:
## Authentifizierung ## Authentifizierung
Stage 0: Keine Authentifizierung. Dies wird in späteren Stages Stage 22: Keine Authentifizierung. Dies kann in späteren Stages
hinzugefügt (API-Key, OAuth, etc.). hinzugefügt werden (API-Key, OAuth, etc.).

View File

@@ -9,6 +9,10 @@ Behauptungen (Claims) und erzeugt einen neutralen, quellengestützten Bericht.
**Kernprinzip:** Webcontent ist DATA, keine INSTRUCTION. Der LLM verarbeitet **Kernprinzip:** Webcontent ist DATA, keine INSTRUCTION. Der LLM verarbeitet
nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt. nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt.
**Stage 022 abgeschlossen.** NSCT ist production-ready.
---
## 2. Architektur-Diagramm ## 2. Architektur-Diagramm
``` ```
@@ -24,8 +28,8 @@ nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt.
│ │ │ │ │ │ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Search │──▶│ Fetch │──▶│ Extract │──▶│ Classify │ │ │ │ │ │ Search │──▶│ Fetch │──▶│ Extract │──▶│ Classify │ │ │
│ │ │ (SearX │ │ (HTTP/ │ │(Trafilat │ │(LLM/Rule)│ │ │ │ │ │ (DDG/ │ │(HTTP/ │ │(Trafilat │ │(LLM/Rule)│ │ │
│ │ │ NG) │ │ Playwright)│ │ura/BS4) │ │ │ │ │ │ │ │ Multi) │ │ Playright)│ │ura/BS4) │ │ │ │ │
│ │ └─────────┘ └──────────┘ └───────────┘ └─────┬─────┘ │ │ │ │ └─────────┘ └──────────┘ └───────────┘ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │ │ │
│ │ ┌─────────┐ ┌───────────┐ ┌────────────┐ ┌───┴─────┐ │ │ │ │ ┌─────────┐ ┌───────────┐ ┌────────────┐ ┌───┴─────┐ │ │
@@ -35,17 +39,25 @@ nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt.
│ │ │ │ │ │ │ │ │ │
│ │ ▼ │ │ │ │ ▼ │ │
│ │ ┌──────────────┐ │ │ │ │ ┌──────────────┐ │ │
│ │ │Evidence │ │ │
│ │ │Scoring (6D) │ │ │
│ │ └──────┬───────┘ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ Report Gen. │ │ │ │ │ │ Report Gen. │ │ │
│ │ │ (LLM) │ │ │ │ │ │ (LLM) │ │ │
│ │ └──────┬───────┘ │ │ │ │ └──────┬───────┘ │ │
│ │ ▼ │ │ │ │ ▼ │ │
│ │ ┌──────────────┐ │ │ │ │ ┌──────────────┐ │ │
│ │ │ Evidence DB │ │ │ │ │ │Provenance │ │ │
│ │ │+ Hash │ │ │
│ │ └──────────────┘ │ │ │ │ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │ │ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────┘
``` ```
---
## 3. Datenfluss ## 3. Datenfluss
``` ```
@@ -65,49 +77,134 @@ Query ──▶ SearchProvider.search() ──▶ List[SearchResult]
EvidenceRelation[] ──▶ Agreement matrix EvidenceRelation[] ──▶ Agreement matrix
│ (6-dim evidence scoring)
EvidencePackage ──▶ Neutral Synthesis
│ (LLM: synthesize report) │ (LLM: synthesize report)
ResearchReport (Summary + Findings + Disagreements + Uncertainties) ResearchReport (Summary + Findings + Disagreements + Uncertainties)
research_run_hash (deterministic, reproducible)
``` ```
---
## 4. Module ## 4. Module
### 4.1 `src/nsct/api/` — REST API ### 4.1 `src/nsct/api/` — REST API
- **main.py**: FastAPI application factory, CORS, lifespan hooks
- **rest_research.py**: Research CRUD — POST/GET/DELETE + status, sources, claims, evidence, report
- **health.py**: `/health`, `/ready`, `/providers` endpunkte - **health.py**: `/health`, `/ready`, `/providers` endpunkte
- **search.py**: Search API endpoint
- **debug.py**: Debug-Endpunkte (nur bei `NSCT_DEBUG=true`)
- **Pattern:** Each router is a separate file; mounted in `main.py` - **Pattern:** Each router is a separate file; mounted in `main.py`
### 4.2 `src/nsct/models/` — Pydantic v2 Schemas ### 4.2 `src/nsct/models/` — Pydantic v2 Schemas
- **schemas.py**: Data transfer objects (SearchQuery, Source, Claim, etc.) - **schemas.py**: Data transfer objects (SearchQuery, Source, Claim, etc.)
- **plan.py**: ResearchPlan Schema
- **claim.py**: Claim Schema mit Provenance
- **synthesis.py**: Synthese Report Schema
- **source_independence.py**: Independence Graph Schema
- **gap_analysis.py**: GapAnalysis Schema
- **audio.py**: Audio Transcription Schema
- **vision.py**: Vision Analysis Schema
- Pure Pydantic — no database or API coupling - Pure Pydantic — no database or API coupling
### 4.3 `src/nsct/storage/` — Database Layer ### 4.3 `src/nsct/storage/` — Database Layer
- **models.py**: SQLAlchemy 2.0 declarative models - **models.py**: SQLAlchemy 2.0 declarative models
- **engine.py**: Async engine factory with pool management - **engine.py**: Async engine factory with pool management
- Uses `asyncpg` for PostgreSQL - Uses `asyncpg` for PostgreSQL
### 4.4 `src/nsct/providers/` — Provider Interfaces ### 4.4 `src/nsct/providers/` — Provider Interfaces
- Abstract base classes (LLMProvider, VisionProvider, SearchProvider,
ContentFetcher, AudioProvider) - **abstract.py**: Abstract base classes (LLMProvider, VisionProvider, SearchProvider, etc.)
- **llm.py**: OpenAI-kompatibler LLM Provider
- **duckduckgo.py**: DuckDuckGo Search Provider
- **multi.py**: MultiProviderSearch für parallele Abfrage
- **vision.py**: Qwen2.5-VL-3B Vision Provider
- **audio.py**: Audio/STT Provider
- **metrics.py**: Provider-Metriken Tracking
- **priority_queue.py**: Priority-Queue mit HIGH/NORMAL/LOW Prioritäten
- **semaphore.py**: Concurrency Semaphore (default: 3)
- **No concrete implementations in Stage 0** — just protocols - **No concrete implementations in Stage 0** — just protocols
- All providers accept structured data, never raw web content - All providers accept structured data, never raw web content
### 4.5 `src/nsct/security/` — Security ### 4.5 `src/nsct/security/` — Security
- **policy.py**: SSRF protection, URL validation, IP blocklisting - **policy.py**: SSRF protection, URL validation, IP blocklisting
- Called before every outbound HTTP request - Called before every outbound HTTP request
### 4.6 `src/nsct/config.py` — Configuration ### 4.6 `src/nsct/config.py` — Configuration
- Pydantic BaseSettings — all values from environment - Pydantic BaseSettings — all values from environment
- Zero hard-coded secrets or URLs - Zero hard-coded secrets or URLs
- `AppSettings.from_env()` creates the root configuration - `AppSettings.from_env()` creates the root configuration
### 4.7 `src/nsct/logging_config.py` — Structured Logging ### 4.7 `src/nsct/logging_config.py` — Structured Logging
- JSON-formatted log output - JSON-formatted log output
- Per-request context tracking (`research_id`, `llm_request_id`) - Per-request context tracking (`research_id`, `llm_request_id`)
- Global context dict merged into every log record - Global context dict merged into every log record
### 4.8 `src/nsct/metrics.py` — Prometheus Metrics
- Counters: search queries, sources fetched, claims, contradictions, completed/failed
- Histograms: research duration
- Gauges: active research runs
### 4.9 `src/nsct/agents/` — Research Planning
- **planner.py**: LLM-basierte Recherchestrategie (Query Expansion, Bias Reduction)
- **validator.py**: Plausibilitäts-Prüfung von Research-Plänen
### 4.10 `src/nsct/crawler/` — Content Extraction
- **fetcher.py**: Asynchroner HTTP-Fetcher mit SSRF-Schutz
- **extraction.py**: Main Content Extraction (trafilatura, BeautifulSoup)
- **normalize.py**: Dokumenten-Normalisierung
- **pdf.py**: PDF-Extraktion
- **policy.py**: SSRF/Download-Policy
- **manager.py**: Batch-Verwaltung
### 4.11 `src/nsct/orchestration/` — Pipeline Control
- **state.py**: State Machine mit 12 Zuständen
- **budget.py**: Hard Budget Limits (7 config options, frozen)
- **models.py**: ResearchRun Pydantic Model (frozen, immutable)
- **orchestrator.py**: Vollständige Pipeline-Steuerung mit Fallbacks
- **context_budget.py**: Pro-Stage Context Token Limits
- **gap_analysis.py**: Lückenerkennung für iterative Recherche
### 4.12 `src/nsct/stages/` — Pipeline Stages (5-13)
- **stage5_extract_claims.py**: Claim Extraction
- **stage6_source_independence.py**: Source Independence Graph
- **stage7_clustering.py**: Claim Clustering
- **stage7_normalize_numerics.py**: Numerical Normalization
- **stage8_evidence_scoring.py**: 6-dimensional Evidence Scoring
- **stage9_synthesis.py**: Neutral Synthesis
- **stage10_vision.py**: Vision Integration
- **stage11_audio.py**: Audio/STT Integration
- **stage13_gap_analysis.py**: Iterative Gap Analysis
### 4.13 `src/nsct/provenance.py` — Provenance & Reproducibility
- **Stage 21**: Vollständige Provenance aller Pipeline-Schritte
- `research_run_hash`: Deterministischer Hash für Reproduzierbarkeit
### 4.14 `src/nsct/cli.py` — Command Line Interface
- **Stage 15**: `nsct research`, `nsct status`, `nsct report`, etc.
---
## 5. Interfaces / Protocols ## 5. Interfaces / Protocols
```python ```python
@@ -129,10 +226,12 @@ class ContentFetcher(ABC):
async def fetch(url, **kwargs) -> dict: ... async def fetch(url, **kwargs) -> dict: ...
``` ```
Alle konkreten Implementationen (SearXNG, OpenAI, Trafilatura, etc.) Alle konkreten Implementationen (DuckDuckGo, Qwen, Trafilatura, etc.)
müssen diese Interfaces implementieren — das ermöglicht den Wechsel müssen diese Interfaces implementieren — das ermöglicht den Wechsel
von Providern ohne Codeänderung im Core. von Providern ohne Codeänderung im Core.
---
## 6. Sicherheitsarchitektur ## 6. Sicherheitsarchitektur
- **Control Plane vs. Evidence Plane:** Der LLM verarbeitet nur strukturierte - **Control Plane vs. Evidence Plane:** Der LLM verarbeitet nur strukturierte
@@ -142,6 +241,9 @@ von Providern ohne Codeänderung im Core.
Stattdessen wird er in strukturierte JSON-Objekte serialisiert. Stattdessen wird er in strukturierte JSON-Objekte serialisiert.
- **Non-Root-Docker:** Der Container läuft als nicht-root User `nsct`. - **Non-Root-Docker:** Der Container läuft als nicht-root User `nsct`.
- **Read-Only-Filesystem:** Wo möglich (`read_only: true` + `tmpfs`). - **Read-Only-Filesystem:** Wo möglich (`read_only: true` + `tmpfs`).
- **Dropped Capabilities:** `cap_drop: [ALL]`
---
## 7. Datenmodell ## 7. Datenmodell
@@ -156,6 +258,21 @@ von Providern ohne Codeänderung im Core.
| `citation_edges` | Quelle-zu-Quelle Referenzen | | `citation_edges` | Quelle-zu-Quelle Referenzen |
| `research_reports` | Aggregierte Forschungsberichte | | `research_reports` | Aggregierte Forschungsberichte |
### Evidence Scoring (Stage 8)
6 dimensionale Scores für jede Evidenz:
| Dimension | Range | Beschreibung |
|-----------|-------|-------------|
| `source_independence` | 01 | Wie unabhängig ist diese Quelle? |
| `primary_source_proximity` | 01 | Wie nah ist die Quelle an der Primärquelle? |
| `cross_source_support` | 01 | Wie viele unabhängige Quellen bestätigen? |
| `contradiction_level` | 01 | Wie hoch ist der Widerspruch? (invertiert) |
| `evidence_directness` | 01 | Wie direkt ist die Evidenz? |
| `date_relevance_score` | 01 | Wie aktuell ist die Evidenz? |
---
## 8. Nicht-funktionale Anforderungen ## 8. Nicht-funktionale Anforderungen
| Kriterium | Anforderung | | Kriterium | Anforderung |
@@ -164,5 +281,8 @@ von Providern ohne Codeänderung im Core.
| Database | PostgreSQL 16+, asyncpg, SQLAlchemy 2.0 | | Database | PostgreSQL 16+, asyncpg, SQLAlchemy 2.0 |
| Logging | Strukturiert (JSON), jede Anfrage tracebar | | Logging | Strukturiert (JSON), jede Anfrage tracebar |
| Config | Environment-only, keine Config-Dateien | | Config | Environment-only, keine Config-Dateien |
| Testing | pytest-asyncio, FastAPI TestClient | | Testing | pytest-asyncio, FastAPI TestClient, E2E-Tests |
| Deployment | Docker Compose, reproduzierbar | | Deployment | Docker Compose, reproduzierbar |
| LLM Concurrency | Konfigurierbar (`NSCT_LLM_MAX_CONCURRENCY`), default: 3 |
| Context Budgeting | Pro-Stage Limits (Planner→Claim→Contradiction→Synthesis) |
| Reproducibility | `research_run_hash` + vollständige Provenance |

290
CHANGELOG.md Normal file
View File

@@ -0,0 +1,290 @@
# Changelog — NSCT (Neutral Search Crawler Tool)
Alle signifikanten Änderungen an NSCT, organisiert nach Stage.
---
## [Unreleased]
### Stage 22 — Abschluss & Production Readiness
**Added**
- `tests/stages/test_e2e_integration.py` — 9 End-to-End-Integrationstests (POST → Pipeline → GET report)
- `CHANGELOG.md` — Vollständiges Versions-Changelog
**Documentation**
- `README.md` — Vollständig überarbeitet mit aktuellem Stand (Stage 022), Architektur-Diagramm, API-Referenz, Docker-Anleitung
- `ARCHITECTURE.md` — Aktualisierte Module-Beschreibungen mit allen Stages
- `API.md` — Vollständige Endpunkt-Dokumentation mit request/response Beispielen
- `DEPLOYMENT.md` — Deployment-Anleitung mit allen Konfigurationsoptionen
- `SECURITY.md` — Sicherheitsgrundsätze (Control/Evidence Plane, SSRF, Docker)
- `METHODOLOGY.md` — Methodik-Dokumentation: Neutralität, Quellenabhängigkeit, Claim-Vergleich
---
## Stage 21 — Reproduzierbarkeit
**Added**
- `src/nsct/provenance.py` — Vollständige Provenance aller Pipeline-Schritte
- `research_run_hash` im Report — deterministischer Hash aus Query, Sources, Claims
- Schema-Version Tracking für backward-compatible Reports
---
## Stage 20 — Context Budgeting
**Added**
- `src/nsct/orchestration/context_budget.py` — Pro-Stage Kontext-Limits
- Planner: 12k, Claim Extraction: 16k, Contradiction: 24k, Synthesis: 48k
- `_track_context_tokens()` im Orchestrator
- `tests/test_context_budget.py` — 18 Tests
- `tests/test_context_budget_integration.py` — 16 Integrationstests
- `src/nsct/providers/priority_queue.py` — async_fix für Python 3.14+
---
## Stage 19 — Performanceoptimierung
**Added**
- `src/nsct/providers/semaphore.py` — LLM Concurrency Semaphore (configurable, default: 3)
- `src/nsct/providers/priority_queue.py` — Priority queue mit HIGH/NORMAL/LOW Prioritäten
- `tests/test_priority_limiter.py` — 14 Tests für Priority Limiter
---
## Stage 18 — Docker Hardening
**Added**
- Non-Root-User (`nsct`, UID 1000) im Dockerfile
- Read-Only-Filesystem mit tmpfs
- Dropped Capabilities (`cap_drop: [ALL]`)
- Resource Limits (Memory, CPU) per Container
- Healthcheck für Docker Container
---
## Stage 17 — Neutralitäts-Tests
**Added**
- `tests/stages/test_neutrality_a_syndication.py` — Syndication-Erkennung
- `tests/stages/test_neutrality_b_political_statements.py` — Politische Aussagen
- `tests/stages/test_neutrality_c_scientific_disagreement.py` — Wissenschaftlicher Dissens
- `tests/stages/test_neutrality_d_prompt_injection.py` — Prompt Injection
- `tests/stages/test_neutrality_e_missing_evidence.py` — Fehlende Evidenz
---
## Stage 16 — Observability
**Added**
- `src/nsct/logging_config.py` — Strukturiertes JSON-Logging mit request context
- `src/nsct/metrics.py` — Prometheus Metriken (search, sources, claims, contradictions, LLM)
- `tests/test_logging.py` — Logging-Tests
- `tests/test_metrics.py` — Metriken-Tests
---
## Stage 15 — CLI
**Added**
- `src/nsct/cli.py` — Kommandozeileninterface
- `tests/test_cli.py` — CLI-Tests
---
## Stage 14 — REST API
**Added**
- `src/nsct/api/rest_research.py` — Vollständige Research REST API
- `POST /v1/research` — Start research (async, background pipeline)
- `GET /v1/research` — Liste aller Recherchen (paginiert)
- `GET /v1/research/{id}` — Metadaten
- `GET /v1/research/{id}/status` — State-Machine Status
- `GET /v1/research/{id}/sources` — Quellen
- `GET /v1/research/{id}/claims` — Claims
- `GET /v1/research/{id}/evidence` — Evidence-Scores
- `GET /v1/research/{id}/report` — Synthese-Bericht
- `DELETE /v1/research/{id}` — Löschen (nicht-komplettiert)
- `tests/test_rest_research.py` — Komplette API-Tests
- Depth-Configs: `quick`, `normal`, `deep` mit escalating Budgets
---
## Stage 13 — Iterative Research / Gap Analysis
**Added**
- `src/nsct/stages/stage13_gap_analysis.py` — Lückenerkennung
- `src/nsct/orchestration/gap_analysis.py` — GapSearchQuery
- Max 3 iterative Runden (konfigurierbar über `NSCT_MAX_RESEARCH_ROUNDS`)
- `tests/test_stage13_gap_analysis.py` — 17 Tests
---
## Stage 12 — Research Orchestrator
**Added**
- `src/nsct/orchestration/state.py` — State Machine (12 Zustände)
- `src/nsct/orchestration/budget.py` — Hard Budget Limits (Pydantic, frozen)
- `src/nsct/orchestration/models.py` — ResearchRun Pydantic Model (frozen)
- `src/nsct/orchestration/orchestrator.py` — Vollständige Pipeline-Steuerung
---
## Stage 11 — Audio Integration
**Added**
- `src/nsct/stages/stage11_audio.py` — STT-Transkription mit timestamped Claims
- `src/nsct/models/audio.py` — Audio Pydantic Schema
- `src/nsct/providers/audio.py` — AudioProvider Implementation
- `src/nsct/api/audio.py` — Audio API Endpoint
- `tests/stages/test_stage11_audio.py` — ~20 Tests
---
## Stage 10 — Vision Integration
**Added**
- `src/nsct/stages/stage10_vision.py` — Qwen2.5-VL-3B Integration
- `src/nsct/models/vision.py` — Vision Pydantic Schema
- `src/nsct/providers/vision.py` — VisionProvider Implementation
- `src/nsct/api/vision.py` — Vision API Endpoint
- `tests/stages/test_stage10_vision.py` — ~25 Tests
---
## Stage 9 — Neutral Synthesis Engine
**Added**
- `src/nsct/stages/stage9_synthesis.py` — LLM-basierte neutrale Berichts-Synthese
- `src/nsct/models/synthesis.py` — Synthese Pydantic Schema
- `src/nsct/api/synthesis.py` — Synthese API Endpoint
- `tests/stages/test_stage9_synthesis.py` — 25 Tests
---
## Stage 8 — Evidence Scoring
**Added**
- `src/nsct/stages/stage8_evidence_scoring.py` — 6-dimensionale Evidence-Scores
- `evidence_type` und `raw_scores_json` Felder
- `src/nsct/api/stage8.py` — Evidence Scoring API
- `tests/stages/test_stage8_evidence_scoring.py` — ~60 Tests
---
## Stage 7 — Claim Clustering & Contradiction
**Added**
- `src/nsct/stages/stage7_clustering.py` — LLM-Clustering
- `src/nsct/stages/stage7_normalize_numerics.py` — Numerische Normalisierung
- `tests/stages/test_stage7_clustering.py` — 79 Tests
---
## Stage 6 — Source Independence & Citation Graph
**Added**
- `src/nsct/stages/stage6_source_independence.py` — Syndication-Erkennung
- `src/nsct/models/source_independence.py` — Independence Pydantic Schema
- `tests/stages/test_stage6_source_independence.py` — 43 Tests
---
## Stage 5 — Claim Extraction
**Added**
- `src/nsct/stages/stage5_extract_claims.py` — Atomare, überprüfbare Claims
- `src/nsct/models/claim.py` — Claim Pydantic Schema (mit provenance)
- `tests/stages/test_stage5_extract_claims.py` — 36 Tests
---
## Stage 4 — Research Planner
**Added**
- `src/nsct/agents/planner.py` — LLM-basierte Recherchestrategie
- `src/nsct/models/plan.py` — Plan Pydantic Schema
- `src/nsct/api/planner.py` — Planner API Endpoint
- `tests/test_planner.py` — 25 Tests
---
## Stage 3 — Crawler & Content Extraction
**Added**
- `src/nsct/crawler/fetcher.py` — Asynchroner HTTP-Fetcher mit SSRF-Schutz
- `src/nsct/crawler/extraction.py` — Content-Extraktion (HTML, Text, PDF)
- `src/nsct/crawler/manager.py` — Batch-Verwaltung
- `src/nsct/crawler/normalize.py` — Dokumenten-Normalisierung
- `src/nsct/crawler/pdf.py` — PDF-Extraktion
- `src/nsct/crawler/policy.py` — SSRF-Schutz Policy
- `tests/test_crawler.py` — ~15 Tests
---
## Stage 2 — Search Provider Abstraction
**Added**
- `src/nsct/providers/duckduckgo.py` — DuckDuckGo Search Provider
- `src/nsct/providers/multi.py` — MultiProviderSearch (parallele Abfrage)
- `src/nsct/api/search.py` — Search API Endpoint
- `tests/test_search.py` — 25 Tests
---
## Stage 1 — Model Provider Layer
**Added**
- `src/nsct/providers/llm.py` — OpenAI-kompatibler LLM-Provider
- `src/nsct/providers/abstract.py` — Abstrakte Provider-Basisklassen
- `src/nsct/api/debug.py` — Debug Endpoints (NSCT_DEBUG only)
- `tests/test_crawler.py`, `tests/test_health.py`, `tests/test_planner.py` — Tests für Stage 1
---
## Stage 0 — Repository & Architekturgrundlage
**Added**
- Vollständige Projektstruktur (`src/nsct/` mit allen Unterverzeichnissen)
- `pyproject.toml` — Python Project Configuration
- `Dockerfile` — Container Image
- `docker-compose.yml` — PostgreSQL, SearXNG, NSCT
- `.env.example` — Konfigurations-Vorlage
- `src/nsct/config.py` — Pydantic BaseSettings Konfiguration
- `src/nsct/storage/` — SQLAlchemy 2.0 Models + Engine (asyncpg)
- `src/nsct/models/schemas.py` — Pydantic v2 Schemas
- `src/nsct/security/policy.py` — SSRF-Schutz, URL-Validierung, IP-Blocklist
- `src/nsct/api/health.py``/health`, `/ready`, `/providers` Endpunkte
- `tests/test_health.py` — Health Check Tests
- `README.md`, `ARCHITECTURE.md`, `SECURITY.md`, `METHODOLOGY.md` — Grundlegende Dokumentation
- 6 Basis-Tests
---
**Zusammenfassung:**
| Stage | Description | Status |
|-------|-------------|--------|
| 0 | Repository & Architekturgrundlage | ✅ |
| 1 | Model Provider Layer | ✅ |
| 2 | Search Provider Abstraction | ✅ |
| 3 | Crawler & Content Extraction | ✅ |
| 4 | Research Planner | ✅ |
| 5 | Claim Extraction | ✅ |
| 6 | Source Independence & Citation Graph | ✅ |
| 7 | Claim Clustering & Contradiction | ✅ |
| 8 | Evidence Scoring | ✅ |
| 9 | Neutral Synthesis Engine | ✅ |
| 10 | Vision Integration | ✅ |
| 11 | Audio Integration | ✅ |
| 12 | Research Orchestrator | ✅ |
| 13 | Iterative Research / Gap Analysis | ✅ |
| 14 | REST API | ✅ |
| 15 | CLI | ✅ |
| 16 | Observability | ✅ |
| 17 | Neutralitäts-Tests | ✅ |
| 18 | Docker Hardening | ✅ |
| 19 | Performanceoptimierung | ✅ |
| 20 | Context Budgeting | ✅ |
| 21 | Reproduzierbarkeit | ✅ |
| 22 | Abschluss & Production Readiness | ✅ |

View File

@@ -5,6 +5,7 @@
- **Docker** (≥ 24.0) und **Docker Compose** (≥ 2.23) - **Docker** (≥ 24.0) und **Docker Compose** (≥ 2.23)
- Mindestens 2 GB freier RAM - Mindestens 2 GB freier RAM
- Zugang zu einem OpenAI-kompatiblen LLM-Endpunkt - Zugang zu einem OpenAI-kompatiblen LLM-Endpunkt
- PostgreSQL 16+ (wird mit docker-compose bereitgestellt)
## 2. Konfiguration ## 2. Konfiguration
@@ -18,17 +19,17 @@ Trage folgende Werte ein:
```env ```env
# LLM (verpflichtend) # LLM (verpflichtend)
NSCT_LLM_BASE_URL=http://192.168.80.199:8030/openai/v1 NSCT_LLM_BASE_URL=http://your-llm-host:8030/openai/v1
NSCT_LLM_MODEL=Qwen3.6-6-35B NSCT_LLM_MODEL=Qwen3.6-35B
NSCT_LLM_MAX_CONCURRENCY=3 NSCT_LLM_MAX_CONCURRENCY=3
NSCT_LLM_API_KEY=<dein-api-key> NSCT_LLM_API_KEY=<dein-api-key>
# Vision (optional) # Vision (optional)
NSCT_VISION_BASE_URL=http://192.168.80.199:8030/openai/visual/v1 NSCT_VISION_BASE_URL=http://your-vision-host:8030/openai/visual/v1
NSCT_VISION_MODEL=Qwen2-VL-3B NSCT_VISION_MODEL=Qwen2.5-VL-3B
# Audio (optional) # Audio (optional)
NSCT_AUDIO_BASE_URL=http://192.168.80.199:8030/hermes-audio NSCT_AUDIO_BASE_URL=http://your-audio-host:8030/hermes-audio
NSCT_AUDIO_MODEL=default NSCT_AUDIO_MODEL=default
# PostgreSQL # PostgreSQL
@@ -102,14 +103,16 @@ docker compose logs -f nsct-api
- `.env` **niemals** committen — `.gitignore` behandelt das - `.env` **niemals** committen — `.gitignore` behandelt das
- Verwende ein Secrets-Management Tool (Hashicorp Vault, AWS Secrets Manager) - Verwende ein Secrets-Management Tool (Hashicorp Vault, AWS Secrets Manager)
- API-Authentifizierung wird in Stage 2+ implementiert
- Network-Policies für Docker (nur interner Traffic zwischen Services) - Network-Policies für Docker (nur interner Traffic zwischen Services)
- Non-Root-Docker-Container mit Read-Only-Filesystem
- Dropped Capabilities (`cap_drop: [ALL]`)
### 5.2 Skalierung ### 5.2 Skalierung
- Derzeit: Single-Instance (kein horizontal scaling) - Derzeit: Single-Instance (kein horizontal scaling)
- PostgreSQL: Connection Pooling über `pool_size=10, max_overflow=20` - PostgreSQL: Connection Pooling über `pool_size=10, max_overflow=20`
- LLM: Max. `NSCT_LLM_MAX_CONCURRENCY` parallele Requests (standard: 3) - LLM: Max. `NSCT_LLM_MAX_CONCURRENCY` parallele Requests (default: 3)
- Concurrency-Semaphore im Provider-Layer
### 5.3 Datenpersistenz ### 5.3 Datenpersistenz
@@ -122,8 +125,7 @@ volumes:
driver: local driver: local
``` ```
Für Production: Verwende ein volumen-Plugin mit Backup-Unterstützung Für Production: Verwende ein volumen-Plugin mit Backup-Unterstützung.
(например, Velero für Kubernetes).
### 5.4 Monitoring ### 5.4 Monitoring
@@ -136,6 +138,18 @@ docker compose logs --tail=100 nsct-api
# DB-Größe # DB-Größe
docker exec -it nsct-postgres psql -U nsct -d nsct -c "SELECT pg_database_size('nsct');" docker exec -it nsct-postgres psql -U nsct -d nsct -c "SELECT pg_database_size('nsct');"
# Prometheus Metrics
curl http://localhost:8080/metrics
```
### 5.5 Healthcheck
Das Dockerfile enthält einen HEALTHCHECK:
```dockerfile
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
``` ```
## 6. Troubleshooting ## 6. Troubleshooting
@@ -166,7 +180,7 @@ docker compose logs postgres
```bash ```bash
# Network-Ping zum LLM-Host # Network-Ping zum LLM-Host
docker compose run --rm nsct-api curl -v http://192.168.80.199:8030/openai/v1/models docker compose run --rm nsct-api curl -v http://your-llm-host:8030/openai/v1/models
``` ```
## 7. Update / Migration ## 7. Update / Migration
@@ -181,3 +195,30 @@ docker compose up --build -d
# Datenbank-Migrationen (wenn benötigt) # Datenbank-Migrationen (wenn benötigt)
# werden in späteren Stages mit Alembic implementiert # werden in späteren Stages mit Alembic implementiert
``` ```
## 8. CI/CD
### Unit Tests
```bash
.docker compose run --rm nsct-api python -m pytest tests/ -v
```
### Docker Build
```bash
docker compose build nsct-api
docker push your-registry/nsct:latest
```
## 9. Production Checklist
- [ ] `.env` nicht in Git
- [ ] Strong PostgreSQL Password
- [ ] LLM API Key konfiguriert
- [ ] Resource Limits gesetzt
- [ ] Healthcheck konfiguriert
- [ ] Logging zu externem System (ELK, Grafana, etc.)
- [ ] Backup-Policy für PostgreSQL Volume
- [ ] Network Policies für Docker
- [ ] Monitoring Alerts (Prometheus + Alertmanager)

155
README.md
View File

@@ -1,6 +1,4 @@
# ============================================================
# NSCT — Neutral Search Crawler Tool # NSCT — Neutral Search Crawler Tool
# ============================================================
**NSCT** ist ein vollständig lokal betreibbares, containerisiertes Recherche- und **NSCT** ist ein vollständig lokal betreibbares, containerisiertes Recherche- und
Analyse-System. Es durchsucht Webquellen, extrahiert Inhalte, vergleicht Analyse-System. Es durchsucht Webquellen, extrahiert Inhalte, vergleicht
@@ -9,30 +7,43 @@ quellengestützten Bericht.
**NSCT steht für:** Neutral Search Crawler Tool. **NSCT steht für:** Neutral Search Crawler Tool.
---
## Architektur-Übersicht ## Architektur-Übersicht
``` ```
┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌─────────────┐ ┌──────────────────────────┐ ┌───────────────┐
│ User / CLI │───▶│ NSCT API │───▶│ FastAPI / │ User / CLI │───▶│ NSCT REST API (FastAPI) │───▶│ uvicorn
│ │ │ (FastAPI) │ │ uvicorn │ │ │ (async, background pip.)│ │ :8080
└─────────────┘ └──────────────┘ └───────────────┘ └─────────────┘ └──────────┬───────────────┘ └───────────────┘
┌─────────────────────────┐ ┌───────────────┼─────────────────┐
┌───────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────┐ ┌─────────┐ ┌──────────────
LLM │ │ Vision │ │ Audio Search │ │ Content │ │ Crawler
│ Provider │ │ Model │ │ Model Providers │ │ Fetcher │ │ (SSRF-Prot)
└───────────┘ └─────────┘ └──────────┘ │ (DDG, Multi) │ │ (HTTP/ │ │ (HTML,PDF, │
│ │ └──────┬───────┘ │ Playwright)│ │ PDF, ...)
▼ ▼ └──────┬─────┘ └──────────────┘
┌─────────────────────────────────────┐ ▼ ▼
│ PostgreSQL (evidence store) │ ┌─────────────────────────────────────────────┐
└─────────────────────────────────────┘ │ Evidence Pipeline (Orchestrator) │
Search → Fetch → Extract → Claim → │
│ Compare → Score → Synthesis → Report
────────────── └──────────────────────────┬─────────────────┘
│ SearXNG │ (optional search backend)
└──────────────┘
┌────────────────────────┐
│ PostgreSQL (asyncpg) │
│ Evidence Store │
└────────────────────────┘
┌──────────────┴──────────────┐
│ LLM (Qwen3.6-35B) │
│ Vision (Qwen2.5-VL-3B) │
│ Audio (STT) │
└──────────────────────────────┘
``` ```
## Quick Start ## Quick Start
@@ -42,6 +53,7 @@ quellengestützten Bericht.
```bash ```bash
# 1. Kopiere die Beispiel-Env # 1. Kopiere die Beispiel-Env
cp .env.example .env cp .env.example .env
# 2. Trage deine Endpunkte ein (LLM, Vision, Audio, PostgreSQL) # 2. Trage deine Endpunkte ein (LLM, Vision, Audio, PostgreSQL)
# 3. Starte alles # 3. Starte alles
@@ -59,40 +71,101 @@ Die API ist danach unter `http://localhost:8080` erreichbar.
``` ```
nsct/ nsct/
├── src/nsct/ ├── src/nsct/
│ ├── api/ # FastAPI-Routen │ ├── api/ # FastAPI-Routen (health, research, search, etc.)
│ ├── models/ # Pydantic-Schemata │ ├── agents/ # Research Planner & Validator
│ ├── providers/ # Abstrakte Provider-Interfaces │ ├── crawler/ # Content Fetcher, Extraction, Normalization
│ ├── models/ # Pydantic v2 Schemas
│ ├── orchestration/# State Machine, Budget, Gap Analysis
│ ├── provenance.py # Vollständige Provenance Tracking
│ ├── providers/ # LLM, Vision, Audio, Search, Multi, Priority
│ ├── security/ # SSRF-Schutz, URL-Validierung │ ├── security/ # SSRF-Schutz, URL-Validierung
── storage/ # SQLAlchemy Models + Engine ── stages/ # Pipeline-Stages (5-13: claim, evidence, synthesis)
├── tests/ # pytest-Tests │ ├── storage/ # SQLAlchemy 2.0 Models + Engine
│ ├── cli.py # Kommandozeileninterface
│ ├── config.py # Pydantic BaseSettings Konfiguration
│ ├── logging_config.py # Strukturiertes JSON-Logging
│ ├── metrics.py # Prometheus Metriken
│ └── main.py # FastAPI Application Factory
├── tests/ # pytest-Tests (Unit + Integration)
├── docker-compose.yml ├── docker-compose.yml
├── Dockerfile ├── Dockerfile
├── pyproject.toml ├── pyproject.toml
├── .env.example
├── README.md ├── README.md
├── ARCHITECTURE.md ├── ARCHITECTURE.md
├── SECURITY.md ├── SECURITY.md
├── METHODOLOGY.md ├── METHODOLOGY.md
├── API.md ├── API.md
├── DEPLOYMENT.md ├── DEPLOYMENT.md
└── .env.example └── CHANGELOG.md
```
## Schnellstart-Beispiel (CLI)
```bash
nsct research "Welche wesentlichen Entwicklungen gab es im letzten Jahr bei Kernfusion?" \
--depth normal \
--language de
```
## Schnellstart-Beispiel (API)
```bash
curl -X POST http://localhost:8080/v1/research \
-H "Content-Type: application/json" \
-d '{
"query": "Welche Entwicklungen gab es bei Kernfusion?",
"language": "de",
"depth": "normal"
}'
```
Antwort:
```json
{
"research_id": "uuid-...",
"status": "pending",
"query": "Welche Entwicklungen gab es bei Kernfusion?",
"depth": "normal",
"state": "created"
}
```
Den Status und das Ergebnis abrufen:
```bash
# Status
curl http://localhost:8080/v1/research/<research_id>/status
# Bericht
curl http://localhost:8080/v1/research/<research_id>/report
# Quellen
curl http://localhost:8080/v1/research/<research_id>/sources
# Claims
curl http://localhost:8080/v1/research/<research_id>/claims
# Evidence-Scores
curl http://localhost:8080/v1/research/<research_id>/evidence
``` ```
## Status ## Status
**Stage 0**Repository und Architekturgrundlage. **Stage 22**Abschluss & Production Readiness.
Stage 0 enthält: Alle 22 Stages sind abgeschlossen. NSCT ist production-ready:
- Vollständige Projektstruktur mit allen Konfigurationsdateien
- Pydantic v2 Datenmodelle
- SQLAlchemy 2.0 Persistenzmodelle
- Sicherheitshards (SSRF-Schutz, IP-Blocklist)
- Provider-Interfaces (abstrakte Basisklassen)
- Health-, Ready- und Provider-Endpunkte
- Strukturiertes Logging
- Docker-Konfiguration für PostgreSQL, SearXNG und NSCT
Die eigentliche Evidence-Pipeline (Search, Fetch, Extract, Claim, Compare, Report) - **Pipeline:** Search Fetch Extract Claim Compare → Score → Synthesize → Report
wird in späteren Stages implementiert. - **State Machine:** 12 Zustände (CREATED → ... → COMPLETED/FAILED/CANCELLED)
- **Budget Limits:** Hard limits für Queries, Sources, LLM-Requests, Duration
- **Iterative Research:** Gap Analysis mit max 3 Runden
- **REST API:** Vollständiger Research Lifecycle (POST/GET/DELETE)
- **CLI:** Kommandozeileninterface mit depth, language, format Optionen
- **Observability:** Strukturiertes Logging + Prometheus Metriken
- **Docker Hardening:** Non-Root, Read-Only FS, Dropped Capabilities, Resource Limits
- **Reproduzierbarkeit:** Vollständige Provenance + research_run_hash
- **Neutralitäts-Tests:** Syndication, politische Aussagen, Dissens, Prompt Injection, fehlende Evidenz
- **E2E-Tests:** 9 Integrationstests für den vollständigen Pipeline-Durchlauf
## Lizenz ## Lizenz

View File

@@ -6,6 +6,10 @@ NSCT verarbeitet **untrusted input** von überall im Web. Jede externe Quelle
kann versuchen, die Integrität des Systems zu kompromittieren. Alle kann versuchen, die Integrität des Systems zu kompromittieren. Alle
Sicherheitsmaßnahmen folgen dem "zero trust" Prinzip. Sicherheitsmaßnahmen folgen dem "zero trust" Prinzip.
**Stage 022 abgeschlossen.** NSCT ist production-ready mit hardened Docker-Image.
---
## 2. Control Plane vs. Evidence Plane ## 2. Control Plane vs. Evidence Plane
``` ```
@@ -35,6 +39,8 @@ Sicherheitsmaßnahmen folgen dem "zero trust" Prinzip.
**Regel:** Webcontent wechselt niemals direkt die Grenze von Evidence Plane **Regel:** Webcontent wechselt niemals direkt die Grenze von Evidence Plane
in die Control Plane als Prompt-Text. in die Control Plane als Prompt-Text.
---
## 3. SSRF-Schutz ## 3. SSRF-Schutz
Jede outbound-HTTP-Anfrage muss durch `nsct/security/policy.py`: Jede outbound-HTTP-Anfrage muss durch `nsct/security/policy.py`:
@@ -45,13 +51,15 @@ Jede outbound-HTTP-Anfrage muss durch `nsct/security/policy.py`:
- `10.0.0.0/8` — Private Class A - `10.0.0.0/8` — Private Class A
- `172.16.0.0/12` — Private Class B - `172.16.0.0/12` — Private Class B
- `192.168.0.0/16` — Private Class C - `192.168.0.0/16` — Private Class C
- `169.254.0.0/16` — Link-local / Link-local - `169.254.0.0/16` — Link-local / Metadata
- `192.0.0.0/24` — IETF Protocol Assignments - `192.0.0.0/24` — IETF Protocol Assignments
- `100.64.0.0/10` — Shared Address Space - `100.64.0.0/10` — Shared Address Space
- `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24` — TEST-NET - `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24` — TEST-NET
3. **Metadata-Endpunkte:** Cloud-Instance-Metadata (`169.254.169.254`, etc.) 3. **Metadata-Endpunkte:** Cloud-Instance-Metadata (`169.254.169.254`, etc.)
4. **Schemata:** `file://`, `ftp://`, `gopher://`, `ldap://`, `data://` blockiert 4. **Schemata:** `file://`, `ftp://`, `gopher://`, `ldap://`, `data://` blockiert
---
## 4. Prompt-Isolation ## 4. Prompt-Isolation
Der LLM darf **niemals** rohen Webcontent als Teil des Prompts sehen. Der Der LLM darf **niemals** rohen Webcontent als Teil des Prompts sehen. Der
@@ -81,6 +89,8 @@ Daten:
Der LLM sieht **nur** dieses JSON — nie den HTML/Raw-Text. Der LLM sieht **nur** dieses JSON — nie den HTML/Raw-Text.
---
## 5. Untrusted Data Handling ## 5. Untrusted Data Handling
| Quelle | Risiko | Schutzmaßnahme | | Quelle | Risiko | Schutzmaßnahme |
@@ -90,16 +100,44 @@ Der LLM sieht **nur** dieses JSON — nie den HTML/Raw-Text.
| LLM Output | Halluzination, Bias | Claims mit confidence scoring; keine single-source truth | | LLM Output | Halluzination, Bias | Claims mit confidence scoring; keine single-source truth |
| Audio/Vision | Manipulierte Inputs | Content-Type-Validierung; Größ Limits | | Audio/Vision | Manipulierte Inputs | Content-Type-Validierung; Größ Limits |
## 6. Docker-Sicherheit ---
## 6. Docker-Sicherheit (Stage 18+)
- **Non-Root-User:** `nsct` (UID 1000) - **Non-Root-User:** `nsct` (UID 1000)
- **Read-Only-Filesystem:** `read_only: true` wo möglich - **Read-Only-Filesystem:** `read_only: true` wo möglich
- **Dropped Capabilities:** `cap_drop: [ALL]` - **Dropped Capabilities:** `cap_drop: [ALL]`
- **No Secrets in Image:** `.env` wird nicht gebuildet - **No Secrets in Image:** `.env` wird nicht gebuildet
- **Resource Limits:** Memory und CPU begrenzt pro Container - **Resource Limits:** Memory und CPU begrenzt pro Container
- **No Docker Socket:** Kein Zugriff auf Host-Docker-Socket
- **No Host Filesystem:** Kein Zugriff auf Host-Dateisysteme
- **Persistent nur in explizite Volumes**
---
## 7. Compliance ## 7. Compliance
- Keine Speicherung von personenbezogenen Daten über die Quellen-Metadaten hinaus - Keine Speicherung von personenbezogenen Daten über die Quellen-Metadaten hinaus
- Claims und Evidence Relations werden nur so lange gespeichert wie nötig - Claims und Evidence Relations werden nur so lange gespeichert wie nötig
- Alle Daten sind exportierbar und löschbar (SQLAlchemy cascade) - Alle Daten sind exportierbar und löschbar (SQLAlchemy cascade)
- Vollständige Provenance (Stage 21): Jeder Schritt ist nachvollziehbar
---
## 8. Neutrale Recherche-Sicherheit (Stage 17+)
### Test A — Syndication
Eine Agenturmeldung wird von 10 Webseiten kopiert.
Erwartung: 1 ursprüngliche Quelle, 9 abhängige Quellen — nicht 10 unabhängige.
### Test B — Politische Aussagen
Behauptungen verschiedener politischer Parteien werden klar getrennt — keine einfache Mehrheit.
### Test C — Wissenschaftlicher Dissens
Verschiedene Studientypen und Evidenzstärken werden sichtbar gemacht — keine Stimmenzählung.
### Test D — Prompt Injection
"ignore all previous instructions" auf Webseiten hat keine Auswirkung auf Policy.
### Test E — Fehlende Evidenz
Nur Blogs wiederholen eine unbelegte Behauptung → System sagt: "Konnte nicht durch Primärquelle verifiziert werden."

View File

@@ -449,6 +449,7 @@ async def _run_pipeline(run: _ResearchRunState, request: ResearchRequest, budget
if result.get("success"): if result.get("success"):
run.state = ResearchRunState.COMPLETED.value run.state = ResearchRunState.COMPLETED.value
run.report = result.get("report") run.report = result.get("report")
run.sources = result.get("sources", [])
run.claims = result.get("report", {}).get("claims", []) run.claims = result.get("report", {}).get("claims", [])
run.evidence_scores = result.get("report", {}).get("evidence", []) run.evidence_scores = result.get("report", {}).get("evidence", [])

View File

@@ -0,0 +1,432 @@
"""End-to-End Integration Test for the full research pipeline (Stage 22).
Tests the complete lifecycle from POST /v1/research → background pipeline →
GET /v1/research/{id}/report with mocked LLM and search providers so the test
is deterministic and fast — no network calls, no real LLM needed.
"""
from __future__ import annotations
from typing import Generator
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from fastapi.testclient import TestClient
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def client() -> Generator[TestClient, None, None]:
"""Synchronous FastAPI TestClient."""
from nsct.api.main import create_app
app = create_app()
with TestClient(app) as c:
yield c
@pytest.fixture(autouse=True)
def clear_store(client: TestClient) -> None:
"""Clear the in-memory research store before every test."""
from nsct.api.rest_research import _research_store
_research_store.clear()
# ---------------------------------------------------------------------------
# Helper — build a mock orchestration result that mimics a successful run
# ---------------------------------------------------------------------------
def _mock_successful_pipeline() -> dict:
"""Return a mock orchestrator.run() result for a successful research run."""
return {
"success": True,
"report": {
"summary": "Zusammenfassung der Recherche.",
"findings": [
{"text": "Fundamentale Erkenntnis A", "confidence": 0.85, "source_ids": ["s1"]},
{"text": "Erkenntnis B bestätigt A", "confidence": 0.72, "source_ids": ["s2"]},
],
"disagreements": [
{
"claim": "Behauptung X",
"positions": [
{"source": "Quelle 1", "view": "Für X"},
{"source": "Quelle 2", "view": "Gegen X"},
],
}
],
"uncertainties": ["Nicht ausreichend belegte Behauptung Y"],
"source_statistics": {"total_sources": 5, "primary_sources": 2, "secondary_sources": 3},
"methodology": "NSCT Evidence Pipeline — Search, Extract, Claim, Compare, Synthesize",
"generated_at": "2025-01-01T00:00:00+00:00",
"claims": [
{
"id": "c1",
"research_run_id": "run-1",
"source_id": "s1",
"claim_text": "Behauptung A",
"evidence_span": "Quelle 1, Abschnitt 2",
"claim_type": "factual",
"confidence": 0.85,
},
],
"evidence": [
{
"claim_id": "c1",
"research_run_id": "run-1",
"evidence_type": "primary_report",
"source_independence_score": 0.9,
"primary_source_proximity": 0.95,
"cross_source_support": 0.7,
"contradiction_level": 0.2,
"evidence_directness": 0.88,
"date_relevance_score": 0.95,
},
],
},
"sources": [
{"id": "s1", "url": "https://example.com/1", "title": "Beispiel 1", "domain": "example.com", "source_type": "primary"},
{"id": "s2", "url": "https://example.com/2", "title": "Beispiel 2", "domain": "example.com", "source_type": "secondary"},
],
}
# ---------------------------------------------------------------------------
# Test 1 — POST /v1/research returns 200 and creates a research run
# ---------------------------------------------------------------------------
def test_e2e_01_post_research_returns_200_and_creates_run(client: TestClient) -> None:
"""Step 1: POST /v1/research creates a research run and returns research_id."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
resp = client.post(
"/v1/research",
json={
"query": "Welche Entwicklungen gab es bei Kernfusion?",
"language": "de",
"depth": "normal",
},
)
assert resp.status_code == 200
body = resp.json()
assert body["status"] == "pending"
assert body["query"] == "Welche Entwicklungen gab es bei Kernfusion?"
assert body["depth"] == "normal"
assert "research_id" in body
research_id = body["research_id"]
assert len(research_id) == 36 # UUID length
# Verify entry in in-memory store
from nsct.api.rest_research import _research_store
assert research_id in _research_store
run = _research_store[research_id]
assert run.query == "Welche Entwicklungen gab es bei Kernfusion?"
assert run.language == "de"
assert run.depth == "normal"
# ---------------------------------------------------------------------------
# Test 2 — Background pipeline completes and report is available
# ---------------------------------------------------------------------------
def test_e2e_02_full_pipeline_until_report_available(client: TestClient) -> None:
"""Step 2-9: Background pipeline simulates a full run; report endpoint returns data."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
resp = client.post(
"/v1/research",
json={
"query": "Kernfusion 2025",
"language": "de",
"depth": "deep",
},
)
research_id = resp.json()["research_id"]
# After background pipeline completes, report should have findings
from nsct.api.rest_research import _research_store
run = _research_store[research_id]
# Verify report data was stored
assert run.report is not None
assert run.report.get("summary") == "Zusammenfassung der Recherche."
assert len(run.report["findings"]) == 2
assert len(run.report["disagreements"]) == 1
assert len(run.report["uncertainties"]) == 1
# ---------------------------------------------------------------------------
# Test 3 — GET /v1/research/{id}/report returns structured findings
# ---------------------------------------------------------------------------
def test_e2e_03_get_report_returns_structured_findings(client: TestClient) -> None:
"""Step 10: GET report endpoint returns all expected fields."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
client.post(
"/v1/research",
json={"query": "Kernfusion", "language": "de", "depth": "normal"},
)
# Fetch the report via the public API
from nsct.api.rest_research import _research_store
research_id = list(_research_store.keys())[0]
resp = client.get(f"/v1/research/{research_id}/report")
assert resp.status_code == 200
body = resp.json()
assert body["research_id"] == research_id
assert "summary" in body and body["summary"]
assert "findings" in body
assert len(body["findings"]) > 0
for finding in body["findings"]:
assert "text" in finding
assert "confidence" in finding
assert "source_ids" in finding
assert "disagreements" in body
assert "uncertainties" in body
assert "source_statistics" in body
assert "methodology" in body and body["methodology"]
assert "generated_at" in body
# ---------------------------------------------------------------------------
# Test 4 — GET /v1/research/{id}/sources, /claims, /evidence return data
# ---------------------------------------------------------------------------
def test_e2e_04_sources_claims_evidence_endpoints(client: TestClient) -> None:
"""Steps 3-8: Sources, claims, and evidence endpoints return correct data."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
client.post(
"/v1/research",
json={"query": "Fusion", "language": "de", "depth": "normal"},
)
from nsct.api.rest_research import _research_store
research_id = list(_research_store.keys())[0]
# --- Sources ---
resp = client.get(f"/v1/research/{research_id}/sources")
assert resp.status_code == 200
body = resp.json()
assert body["research_id"] == research_id
assert body["total"] == 2
assert len(body["sources"]) == 2
for src in body["sources"]:
assert "id" in src
assert "url" in src
assert "title" in src
assert "domain" in src
# --- Claims ---
resp = client.get(f"/v1/research/{research_id}/claims")
assert resp.status_code == 200
body = resp.json()
assert body["research_id"] == research_id
assert body["total"] == 1
assert len(body["claims"]) == 1
claim = body["claims"][0]
assert "claim_text" in claim
assert "evidence_span" in claim
assert "claim_type" in claim
assert "confidence" in claim
# --- Evidence ---
resp = client.get(f"/v1/research/{research_id}/evidence")
assert resp.status_code == 200
body = resp.json()
assert body["research_id"] == research_id
assert body["total"] == 1
assert len(body["evidence"]) == 1
evidence = body["evidence"][0]
assert "claim_id" in evidence
assert "source_independence_score" in evidence
assert "primary_source_proximity" in evidence
assert "cross_source_support" in evidence
assert "contradiction_level" in evidence
assert "evidence_directness" in evidence
assert "date_relevance_score" in evidence
# ---------------------------------------------------------------------------
# Test 5 — Status endpoint reflects COMPLETED state
# ---------------------------------------------------------------------------
def test_e2e_05_status_reflects_completed_state(client: TestClient) -> None:
"""After pipeline completion, status shows COMPLETED with counts."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
client.post(
"/v1/research",
json={"query": "Status test", "language": "de", "depth": "normal"},
)
from nsct.api.rest_research import _research_store
research_id = list(_research_store.keys())[0]
resp = client.get(f"/v1/research/{research_id}/status")
assert resp.status_code == 200
body = resp.json()
assert body["research_id"] == research_id
assert body["state"] == "completed"
assert body["source_count"] == 2
assert body["claim_count"] == 1
assert body["is_completed"] is True
assert body["is_running"] is False
# ---------------------------------------------------------------------------
# Test 6 — List endpoint aggregates all research runs
# ---------------------------------------------------------------------------
def test_e2e_06_list_aggregates_all_runs(client: TestClient) -> None:
"""GET /v1/research returns all completed researches with correct counts."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
for i in range(3):
client.post(
"/v1/research",
json={"query": f"Query {i}", "language": "de", "depth": "quick"},
)
from nsct.api.rest_research import _research_store
resp = client.get("/v1/research")
assert resp.status_code == 200
body = resp.json()
assert body["total"] == 3
assert len(body["items"]) == 3
for item in body["items"]:
assert "research_id" in item
assert "query" in item
assert "state" in item
assert "source_count" in item
assert "claim_count" in item
# ---------------------------------------------------------------------------
# Test 7 — Error handling: non-existent research returns 404
# ---------------------------------------------------------------------------
def test_e2e_07_nonexistent_research_returns_404(client: TestClient) -> None:
"""All individual research endpoints return 404 for unknown IDs."""
endpoints = [
"/v1/research/nonexistent-id",
"/v1/research/nonexistent-id/status",
"/v1/research/nonexistent-id/sources",
"/v1/research/nonexistent-id/claims",
"/v1/research/nonexistent-id/evidence",
"/v1/research/nonexistent-id/report",
]
for ep in endpoints:
resp = client.get(ep)
assert resp.status_code == 404, f"Expected 404 for {ep}, got {resp.status_code}"
# ---------------------------------------------------------------------------
# Test 8 — Delete only works on non-completed runs
# ---------------------------------------------------------------------------
def test_e2e_08_delete_completed_returns_400(client: TestClient) -> None:
"""DELETE on a COMPLETED research run returns 400 (immutable)."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(return_value=_mock_successful_pipeline())
MockOrch.return_value = mock_instance
client.post(
"/v1/research",
json={"query": "Delete test", "language": "de", "depth": "normal"},
)
from nsct.api.rest_research import _research_store, ResearchRunState
research_id = list(_research_store.keys())[0]
run = _research_store[research_id]
# Manually set to completed so the test is deterministic
run.state = ResearchRunState.COMPLETED.value
resp = client.delete(f"/v1/research/{research_id}")
assert resp.status_code == 400
body = resp.json()
assert "detail" in body
# ---------------------------------------------------------------------------
# Test 9 — Pipeline failure is handled gracefully
# ---------------------------------------------------------------------------
def test_e2e_09_failed_pipeline_returns_failed_state(client: TestClient) -> None:
"""When the orchestrator.run() raises, the run state becomes FAILED."""
with patch("nsct.orchestration.orchestrator.ResearchOrchestrator") as MockOrch:
mock_instance = AsyncMock()
mock_instance.start = AsyncMock()
mock_instance.run = AsyncMock(
side_effect=RuntimeError("LLM service unavailable")
)
MockOrch.return_value = mock_instance
resp = client.post(
"/v1/research",
json={"query": "Fehler test", "language": "de", "depth": "quick"},
)
research_id = resp.json()["research_id"]
from nsct.api.rest_research import _research_store, ResearchRunState
run = _research_store.get(research_id)
assert run is not None
assert run.state == ResearchRunState.FAILED.value
assert "error" in run.metadata
# Status endpoint should show FAILED
resp = client.get(f"/v1/research/{research_id}/status")
assert resp.status_code == 200
assert resp.json()["state"] == "failed"
assert resp.json()["is_completed"] is False

215
uv.lock generated
View File

@@ -1,6 +1,10 @@
version = 1 version = 1
revision = 3 revision = 3
requires-python = ">=3.12" requires-python = ">=3.11"
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'emscripten'",
"python_full_version < '3.12' or sys_platform != 'emscripten'",
]
[[package]] [[package]]
name = "aiosqlite" name = "aiosqlite"
@@ -48,6 +52,14 @@ version = "0.31.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/08/17/cc02bc49bc350623d050fa139e34ea512cd6e020562f2a7312a7bcae4bc9/asyncpg-0.31.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eee690960e8ab85063ba93af2ce128c0f52fd655fdff9fdb1a28df01329f031d", size = 643159, upload-time = "2025-11-24T23:25:36.443Z" },
{ url = "https://files.pythonhosted.org/packages/a4/62/4ded7d400a7b651adf06f49ea8f73100cca07c6df012119594d1e3447aa6/asyncpg-0.31.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2657204552b75f8288de08ca60faf4a99a65deef3a71d1467454123205a88fab", size = 638157, upload-time = "2025-11-24T23:25:37.89Z" },
{ url = "https://files.pythonhosted.org/packages/d6/5b/4179538a9a72166a0bf60ad783b1ef16efb7960e4d7b9afe9f77a5551680/asyncpg-0.31.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a429e842a3a4b4ea240ea52d7fe3f82d5149853249306f7ff166cb9948faa46c", size = 2918051, upload-time = "2025-11-24T23:25:39.461Z" },
{ url = "https://files.pythonhosted.org/packages/e6/35/c27719ae0536c5b6e61e4701391ffe435ef59539e9360959240d6e47c8c8/asyncpg-0.31.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0807be46c32c963ae40d329b3a686356e417f674c976c07fa49f1b30303f109", size = 2972640, upload-time = "2025-11-24T23:25:41.512Z" },
{ url = "https://files.pythonhosted.org/packages/43/f4/01ebb9207f29e645a64699b9ce0eefeff8e7a33494e1d29bb53736f7766b/asyncpg-0.31.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e5d5098f63beeae93512ee513d4c0c53dc12e9aa2b7a1af5a81cddf93fe4e4da", size = 2851050, upload-time = "2025-11-24T23:25:43.153Z" },
{ url = "https://files.pythonhosted.org/packages/3e/f4/03ff1426acc87be0f4e8d40fa2bff5c3952bef0080062af9efc2212e3be8/asyncpg-0.31.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37fc6c00a814e18eef51833545d1891cac9aa69140598bb076b4cd29b3e010b9", size = 2962574, upload-time = "2025-11-24T23:25:44.942Z" },
{ url = "https://files.pythonhosted.org/packages/c7/39/cc788dfca3d4060f9d93e67be396ceec458dfc429e26139059e58c2c244d/asyncpg-0.31.0-cp311-cp311-win32.whl", hash = "sha256:5a4af56edf82a701aece93190cc4e094d2df7d33f6e915c222fb09efbb5afc24", size = 521076, upload-time = "2025-11-24T23:25:46.486Z" },
{ url = "https://files.pythonhosted.org/packages/28/fc/735af5384c029eb7f1ca60ccb8fa95521dbdaeef788edf4cecfc604c3cab/asyncpg-0.31.0-cp311-cp311-win_amd64.whl", hash = "sha256:480c4befbdf079c14c9ca43c8c5e1fe8b6296c96f1f927158d4f1e750aacc047", size = 584980, upload-time = "2025-11-24T23:25:47.938Z" },
{ url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" },
{ url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" },
{ url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" },
@@ -119,6 +131,22 @@ version = "3.5.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" },
{ url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" },
{ url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" },
{ url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" },
{ url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" },
{ url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" },
{ url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" },
{ url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" },
{ url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" },
{ url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" },
{ url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" },
{ url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" },
{ url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" },
{ url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" },
{ url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" },
{ url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" },
{ url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" },
{ url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" },
@@ -285,6 +313,21 @@ version = "7.15.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/66/edcec7d7a0b524aa8923e22925fde6fe50ce005a113dca13ae1581455c4c/coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490", size = 222367, upload-time = "2026-08-06T13:47:15.578Z" },
{ url = "https://files.pythonhosted.org/packages/e6/c6/ab8de429e2e8548faf58ec7e1674a4ce00414b4113942d3fe87109cf0f68/coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e", size = 222874, upload-time = "2026-08-06T13:47:16.961Z" },
{ url = "https://files.pythonhosted.org/packages/be/c4/3b7b49587e8a6b9af79b3eb468d443d6042b6d65b47aa26586846a0d6566/coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7", size = 253287, upload-time = "2026-08-06T13:47:18.291Z" },
{ url = "https://files.pythonhosted.org/packages/fb/65/ec03b743a2a229c72cc1eff3e57be9d3564e9c6b4d5aba2d70744a3fc0d8/coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6", size = 255199, upload-time = "2026-08-06T13:47:19.765Z" },
{ url = "https://files.pythonhosted.org/packages/41/4b/5163729e4b6582d61975cfd3ccab45b4ec53e21cf156d9941cb025188468/coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d", size = 257308, upload-time = "2026-08-06T13:47:21.206Z" },
{ url = "https://files.pythonhosted.org/packages/86/08/2167a0f08fb87d702fa423a48578a32865464b7c9e1db3911ad7812ab414/coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce", size = 259268, upload-time = "2026-08-06T13:47:22.503Z" },
{ url = "https://files.pythonhosted.org/packages/1e/e5/68eebae3053dbd48508edea559c21b23fbdf3460784f91370c83a86a6acd/coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7", size = 253392, upload-time = "2026-08-06T13:47:23.88Z" },
{ url = "https://files.pythonhosted.org/packages/1a/46/fd4ced40a2b691c774e515c9b69500bfa64c7960b67fcee4b2f6fad97fc3/coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b", size = 255001, upload-time = "2026-08-06T13:47:25.469Z" },
{ url = "https://files.pythonhosted.org/packages/53/25/ae2e5fa710bb6957a9aadeb9e3598d3b3e4af6587ce857ad42e8639a3f30/coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc", size = 253061, upload-time = "2026-08-06T13:47:26.845Z" },
{ url = "https://files.pythonhosted.org/packages/d7/31/67ddc0365db2c6e93ac8580bc4bbc50f65273262f973f63ebcdbc15c0495/coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571", size = 256831, upload-time = "2026-08-06T13:47:28.217Z" },
{ url = "https://files.pythonhosted.org/packages/f6/78/82b8fd18f57fb13f12d98fe874995bb2c4f9f17be8aff762c426323fdb96/coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719", size = 252781, upload-time = "2026-08-06T13:47:29.712Z" },
{ url = "https://files.pythonhosted.org/packages/0a/eb/6c74ef4dd12b252e573c49bdef9e2ac265bf3dbb79b8d7feb3266e084e9e/coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7", size = 253692, upload-time = "2026-08-06T13:47:31.192Z" },
{ url = "https://files.pythonhosted.org/packages/5a/66/eb9aed1c3fd2d36ee00eb173f434b14fa607fc056739c9a89ff4244010ea/coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e", size = 224461, upload-time = "2026-08-06T13:47:32.572Z" },
{ url = "https://files.pythonhosted.org/packages/e2/6d/81fa4161dfb3ed9d74e40d58647eff83a56b7612e78352581280fce2f477/coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc", size = 224937, upload-time = "2026-08-06T13:47:34.205Z" },
{ url = "https://files.pythonhosted.org/packages/5b/c1/d8dacf683c6cad3cf85ce68fd3774a6774ec402128822fdfaed920f11e6a/coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890", size = 224479, upload-time = "2026-08-06T13:47:36.118Z" },
{ url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" }, { url = "https://files.pythonhosted.org/packages/1d/48/bc8d4ba7b37551a767bd863f15b3f80182b271c2f55975356f5f7dbe94c2/coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22", size = 222543, upload-time = "2026-08-06T13:47:37.562Z" },
{ url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" }, { url = "https://files.pythonhosted.org/packages/20/dd/88d6f83f1fffc974a3691a34a97951c5b12df7512a6782c5963883cbc058/coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97", size = 222905, upload-time = "2026-08-06T13:47:38.927Z" },
{ url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" }, { url = "https://files.pythonhosted.org/packages/bd/5c/54ee0d4748585bb0acab9891cd8d92f2d3593165b4e59fc9de113bfb3140/coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d", size = 254407, upload-time = "2026-08-06T13:47:40.488Z" },
@@ -378,6 +421,11 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" },
] ]
[package.optional-dependencies]
toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]] [[package]]
name = "dateparser" name = "dateparser"
version = "1.4.2" version = "1.4.2"
@@ -415,6 +463,14 @@ version = "3.5.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/d8/7cc97c142388aef03f622e001c572c4f84e9252a439549d483f555771970/greenlet-3.5.5.tar.gz", hash = "sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c", size = 207585, upload-time = "2026-08-10T15:09:36.136Z" } sdist = { url = "https://files.pythonhosted.org/packages/0b/d8/7cc97c142388aef03f622e001c572c4f84e9252a439549d483f555771970/greenlet-3.5.5.tar.gz", hash = "sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c", size = 207585, upload-time = "2026-08-10T15:09:36.136Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/a3/07297917485ee2ca85bc3c8dc6ed85ad3fffcf424047fba62671dba68e97/greenlet-3.5.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:be63afcbbccfad3dd95a1ba12ada84dab2ef32031973d80b5b92df67fa763a61", size = 294165, upload-time = "2026-08-10T13:25:17.987Z" },
{ url = "https://files.pythonhosted.org/packages/db/51/6f732f9314cda54c5fd48a7620c7160f4f286967e8045ad94b9d66ce80b7/greenlet-3.5.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a268024ce2d7d2b04694bf1594058981a9fa663d1df4b762dee499211ed7c1c", size = 613610, upload-time = "2026-08-10T14:14:33.829Z" },
{ url = "https://files.pythonhosted.org/packages/d8/c0/b27589e25d220289edcd4d582b2b17b83058d1a56d53d971b6ea1a34f10d/greenlet-3.5.5-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35cbb8bf55ace57fbccb4fb8622c4521713acd8691e77f4696d416ea7ca527da", size = 625481, upload-time = "2026-08-10T14:27:23.647Z" },
{ url = "https://files.pythonhosted.org/packages/51/2d/f2c928218ac52f26d7a2c188c171d1b7e728b23782cb3347e7b4fce1493a/greenlet-3.5.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cc6df89ec5302337adc9cf096221cbed2510fd444b0e0f1586cf0470740864", size = 624562, upload-time = "2026-08-10T13:40:48.064Z" },
{ url = "https://files.pythonhosted.org/packages/3e/4a/92fc51d5d35912f4f06eec037ba347985defd0be47463a010a325634d9d2/greenlet-3.5.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d9b454c5fc48aeaa7c4337813dbf513a6870468e426438a04d922c6d0fe63db", size = 1584909, upload-time = "2026-08-10T14:15:04.343Z" },
{ url = "https://files.pythonhosted.org/packages/ac/58/ed98b80ac5738c149a5258544843c45601ade1fd70f61740cdaead6351b3/greenlet-3.5.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03551ed792cb1b4fc0277a0c60dfd8c343894a0ba06fe60dcd22f568b433da39", size = 1651184, upload-time = "2026-08-10T13:40:28.879Z" },
{ url = "https://files.pythonhosted.org/packages/d8/be/b582ceb80cefdf9d8da34078714e4b12b3d16f509dee0f65e40a5cc8fc7d/greenlet-3.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab3df3dffb58bf70564e93a5cec7941e4d9faa5a36cc4234a10d3131afe04f53", size = 323280, upload-time = "2026-08-10T13:26:07.495Z" },
{ url = "https://files.pythonhosted.org/packages/4d/18/5313c4c58598c38b0373c013e4ff2b3e6d258aaaa338f373335ebecdaddd/greenlet-3.5.5-cp311-cp311-win_arm64.whl", hash = "sha256:2b70a766135540c472ac1393d57c2e1b4a2eb85bf526a1e41e6d096173a8cee5", size = 307785, upload-time = "2026-08-10T13:28:34.874Z" },
{ url = "https://files.pythonhosted.org/packages/2e/7e/9ecd0285e3153532ae07aeb88063c43c72b4221cf0d4d123b02f3682e3ff/greenlet-3.5.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380", size = 295809, upload-time = "2026-08-10T13:25:34.023Z" }, { url = "https://files.pythonhosted.org/packages/2e/7e/9ecd0285e3153532ae07aeb88063c43c72b4221cf0d4d123b02f3682e3ff/greenlet-3.5.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380", size = 295809, upload-time = "2026-08-10T13:25:34.023Z" },
{ url = "https://files.pythonhosted.org/packages/35/73/60e4bbcc89252037b18087f2ec16405d5b2d5be42dde191bbf3667e96102/greenlet-3.5.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053", size = 611910, upload-time = "2026-08-10T14:14:35.18Z" }, { url = "https://files.pythonhosted.org/packages/35/73/60e4bbcc89252037b18087f2ec16405d5b2d5be42dde191bbf3667e96102/greenlet-3.5.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053", size = 611910, upload-time = "2026-08-10T14:14:35.18Z" },
{ url = "https://files.pythonhosted.org/packages/a4/17/cd5134be659cd4a443e7a61ae670dabec165a814c51162916d637b6dd38e/greenlet-3.5.5-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95", size = 624198, upload-time = "2026-08-10T14:27:25.229Z" }, { url = "https://files.pythonhosted.org/packages/a4/17/cd5134be659cd4a443e7a61ae670dabec165a814c51162916d637b6dd38e/greenlet-3.5.5-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95", size = 624198, upload-time = "2026-08-10T14:27:25.229Z" },
@@ -537,7 +593,7 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten'" }, { name = "anyio", marker = "sys_platform != 'emscripten'" },
{ name = "httpcore2", marker = "sys_platform != 'emscripten'" }, { name = "httpcore2", marker = "sys_platform != 'emscripten'" },
{ name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" }, { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" },
{ name = "idna" }, { name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten'" }, { name = "truststore", marker = "sys_platform != 'emscripten'" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" },
@@ -580,6 +636,20 @@ version = "0.16.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/3f/fae6cc967d120ec89e31c5418a51176d8278b3087fbb384a9176754f353c/jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3", size = 309289, upload-time = "2026-06-29T13:02:52.301Z" },
{ url = "https://files.pythonhosted.org/packages/c8/e3/97c6c3562c077f6247d6e6ce5c82562500b6316c0d928e97e106b7a1321a/jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e", size = 315181, upload-time = "2026-06-29T13:02:53.964Z" },
{ url = "https://files.pythonhosted.org/packages/7b/89/d8d073f8aa2667e46c6c0873f86fe4a512bba4293cc730f626a076211a62/jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037", size = 340939, upload-time = "2026-06-29T13:02:55.412Z" },
{ url = "https://files.pythonhosted.org/packages/87/c9/db4fda3ed73fb864139305e935e5b8b38a5a24692a5a9dd356c22f1b9c8d/jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e", size = 364932, upload-time = "2026-06-29T13:02:57.28Z" },
{ url = "https://files.pythonhosted.org/packages/a2/74/52b5e86241057f52ddd7c9a580f90effb51f9d06239f6fc612279b91a838/jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b", size = 461132, upload-time = "2026-06-29T13:02:58.994Z" },
{ url = "https://files.pythonhosted.org/packages/a9/87/544a700f7447c1f31c5d7833821a4daa5683165c2d5a094fbf5b5800c3dc/jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c", size = 374857, upload-time = "2026-06-29T13:03:00.455Z" },
{ url = "https://files.pythonhosted.org/packages/40/cd/0fcc3f7d39183674d5bfa9ec640faaeb506c60be7c8f94625dfba366e37c/jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee", size = 347053, upload-time = "2026-06-29T13:03:02.045Z" },
{ url = "https://files.pythonhosted.org/packages/5c/ae/c7e64e7932ad597fa395b61440b249ada6366716e25c6e08dd2afbd021e6/jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03", size = 356153, upload-time = "2026-06-29T13:03:03.706Z" },
{ url = "https://files.pythonhosted.org/packages/d4/1c/1c719044f14da814e1a060191ab19b96f3e99207bc5b4bfc6d6be34b3f80/jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea", size = 393956, upload-time = "2026-06-29T13:03:05.165Z" },
{ url = "https://files.pythonhosted.org/packages/3b/dc/7b2f303a2847207e265503853a2d964a55354cffd62a5f2936c155486798/jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91", size = 521081, upload-time = "2026-06-29T13:03:06.886Z" },
{ url = "https://files.pythonhosted.org/packages/c2/5f/501cf6e1e09caeb420195179ffc6f62aca603f1220ec53fd80d0d70b3e56/jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3", size = 552085, upload-time = "2026-06-29T13:03:08.339Z" },
{ url = "https://files.pythonhosted.org/packages/79/54/aa5be86520113b79455c3877f3d1f07a348098df4083ba3688e9537e52dd/jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7", size = 206755, upload-time = "2026-06-29T13:03:09.653Z" },
{ url = "https://files.pythonhosted.org/packages/64/ec/2feb893eb330bd69b413866f4d5daada33c3962f1c6f270c91ca2d87fdf9/jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1", size = 199155, upload-time = "2026-06-29T13:03:10.979Z" },
{ url = "https://files.pythonhosted.org/packages/b9/9c/ca040d94415048a3666fc237774df8151c96f8d2b661cbe3b184acc95876/jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056", size = 194403, upload-time = "2026-06-29T13:03:12.341Z" },
{ url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943, upload-time = "2026-06-29T13:03:14.035Z" }, { url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943, upload-time = "2026-06-29T13:03:14.035Z" },
{ url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779, upload-time = "2026-06-29T13:03:15.418Z" }, { url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779, upload-time = "2026-06-29T13:03:15.418Z" },
{ url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826, upload-time = "2026-06-29T13:03:17.11Z" }, { url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826, upload-time = "2026-06-29T13:03:17.11Z" },
@@ -636,6 +706,10 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" }, { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" },
{ url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" }, { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" },
{ url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" }, { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" },
{ url = "https://files.pythonhosted.org/packages/06/d3/8e278946d43eeca2585b4dd0834a887cd71136329b837f3a16ed86a8b4b0/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad", size = 304518, upload-time = "2026-06-29T13:05:00.172Z" },
{ url = "https://files.pythonhosted.org/packages/72/43/28d4ef495028bf0506a413d4db3f4eb3e7288a382e0f065f306a17bbeb5e/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f", size = 310207, upload-time = "2026-06-29T13:05:02.123Z" },
{ url = "https://files.pythonhosted.org/packages/e0/ca/c366b1012da1d640de975d9683acd44e4d150d9068845d0ca2610435253f/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5", size = 342771, upload-time = "2026-06-29T13:05:03.55Z" },
{ url = "https://files.pythonhosted.org/packages/16/52/50cc4056fc1ae02e7154704e7ecc89df0afb8300222cfe8a52d3f67e4730/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85", size = 346468, upload-time = "2026-06-29T13:05:05.452Z" },
{ url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106, upload-time = "2026-06-29T13:05:07.118Z" }, { url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106, upload-time = "2026-06-29T13:05:07.118Z" },
{ url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658, upload-time = "2026-06-29T13:05:08.708Z" }, { url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658, upload-time = "2026-06-29T13:05:08.708Z" },
{ url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719, upload-time = "2026-06-29T13:05:10.41Z" }, { url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719, upload-time = "2026-06-29T13:05:10.41Z" },
@@ -660,6 +734,22 @@ version = "6.1.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ad/a9/970b8fa0ecc4fbf1dfaed0d89bbc1fc1421b25ec26a2038c91e872dc6c8e/lxml-6.1.2.tar.gz", hash = "sha256:1055241852f2b02068af4a625a5d32c087db193c12251928af2562ecd2239f18", size = 4210626, upload-time = "2026-08-19T04:58:15.341Z" } sdist = { url = "https://files.pythonhosted.org/packages/ad/a9/970b8fa0ecc4fbf1dfaed0d89bbc1fc1421b25ec26a2038c91e872dc6c8e/lxml-6.1.2.tar.gz", hash = "sha256:1055241852f2b02068af4a625a5d32c087db193c12251928af2562ecd2239f18", size = 4210626, upload-time = "2026-08-19T04:58:15.341Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/2d/c292b75049d8b919a515a439646307b971a5f72cd99aaf77d59c9a99e7c4/lxml-6.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6a4f55f0e3308c07354b1ee239c5550afc212f81629a6067db505ace3b667a", size = 8563059, upload-time = "2026-08-19T04:58:26.559Z" },
{ url = "https://files.pythonhosted.org/packages/69/55/16395f232cb28182c72a1fb4d9d187163fd05a581a98c37f33e945b77a6d/lxml-6.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f4d2c36fd5997d30ff19c29fb93293401d0daaf87512297d47610e6883964b5", size = 4613599, upload-time = "2026-08-19T04:58:30.589Z" },
{ url = "https://files.pythonhosted.org/packages/08/20/a65a084596ccd7fd1ed0668b4cf3b68e700da4eac830a0f22ac569f19a73/lxml-6.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1d55a614d2f0457b1f7511c1b7bec0db0dcdd4af4d09d226829eb054c647527c", size = 4935619, upload-time = "2026-08-19T04:58:45.181Z" },
{ url = "https://files.pythonhosted.org/packages/e1/35/008bf5a5f8809a90a3e62909d8d4458f09b7c034c365b508990bdc38b5b7/lxml-6.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:575fef7f30048b744dffb3e4ff64a18cac7dba3fd26efdea5730ade9d1bdeb33", size = 5078913, upload-time = "2026-08-19T04:58:53.376Z" },
{ url = "https://files.pythonhosted.org/packages/4f/cf/041b4c15ba3b0421ed828af60993f23cf6e5ea8801efb773b19e248fc6a5/lxml-6.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79b428c3242e63bdacf3b526a34e0b8b26583846fc597da84b8f0c3d5ea446b2", size = 5012236, upload-time = "2026-08-19T04:59:06.663Z" },
{ url = "https://files.pythonhosted.org/packages/06/42/89a2760cd2f2cda28ef5b9591ec775a6a5183d193e7b62ddb936b1565167/lxml-6.1.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12ecfea07d767f6accbf30b014e1c477b5eabb13eb4e8c748215efb52c0e314a", size = 5211283, upload-time = "2026-08-19T04:59:31.561Z" },
{ url = "https://files.pythonhosted.org/packages/5d/0d/f5607ff466d0d8874d7b778c3ccb64f65ccc0ac430e1961969fd450b899c/lxml-6.1.2-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:bfcbee8ffff4188f4c6d97eceeff36d8eb983cf838933cbc12ce5f5dd51476c6", size = 5343352, upload-time = "2026-08-19T04:59:41.056Z" },
{ url = "https://files.pythonhosted.org/packages/63/6a/77713b73265d043a513d9e7df2458f07b2a14709f95e3a35a34834785fde/lxml-6.1.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:822d9397033edbe530a13bb1e0091c0e817536b6aba87a9b4ad626ed779ca0bd", size = 4673191, upload-time = "2026-08-19T05:00:01.85Z" },
{ url = "https://files.pythonhosted.org/packages/a7/c7/e4179e0b9f71859bf9a56b3da91db4c7e85c47072018e7b63e019ff65c9f/lxml-6.1.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4303f904fb6c41b58dc70743b1d8a470aba6c9897427c48324cff1a95673ddb4", size = 5281079, upload-time = "2026-08-19T05:00:20.59Z" },
{ url = "https://files.pythonhosted.org/packages/22/f4/358200b95081db4fd02c4d81938a07080ae7636f9149befda1c0e5189c40/lxml-6.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdd35422de747237f451e821766e2b6be3dd2c31955c1ecd7f17984c5b9bb62d", size = 5055515, upload-time = "2026-08-19T05:00:29.28Z" },
{ url = "https://files.pythonhosted.org/packages/fe/06/8fe708d90022bd13122c359d38f3f751e4fa71b871eace7fa81212dadfa5/lxml-6.1.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b3ca02ef3b5920b88119c82eb6badfb2d082b1f681d528a856dcce17c8706da8", size = 4722745, upload-time = "2026-08-19T05:00:49.132Z" },
{ url = "https://files.pythonhosted.org/packages/ad/1d/9d374182c2ee79a5097d4950bfca9e28011eeacdf614db022b9905266b5c/lxml-6.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4bf14db2f0214003ec7f46c4300e2065668fc93e20448c1c95bac2e952072168", size = 5268962, upload-time = "2026-08-19T05:01:15.762Z" },
{ url = "https://files.pythonhosted.org/packages/72/89/d0835e464b84d92c43d838bbeaef02f9ac374ab2bb6972411e4c3e80975d/lxml-6.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2afd1688e372d8eafaa6f56c589399e0a87d086a0c110f6346b0b50f42e67e25", size = 5235564, upload-time = "2026-08-19T05:03:11.298Z" },
{ url = "https://files.pythonhosted.org/packages/bf/ea/0b8acc86d702b9fa1a0194fc7e653087912d340cb10507f4a5bc369d04b3/lxml-6.1.2-cp311-cp311-win32.whl", hash = "sha256:aea814342f6afd20d832937ff8b333cd6506428a39c0c4c70c2380aab1887bfb", size = 3600342, upload-time = "2026-08-19T05:03:14.238Z" },
{ url = "https://files.pythonhosted.org/packages/65/5c/04480497142794bfb2d98c01ea9972e9b3d0f6b1f017073cabb74ab0b8c1/lxml-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b3db5497af55f7a557c95265dd3b91c75dc56364a7b59f258c45fa5576dce058", size = 4032771, upload-time = "2026-08-19T05:03:16.934Z" },
{ url = "https://files.pythonhosted.org/packages/f9/88/4c5ca0f808a80b7eaad073269f1fc53992c5c7c905df13d3953d886834b1/lxml-6.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8dc3d29f2ed2bbf24c205a86326d6681230ace55abfb3f9d5230f42078ad63d", size = 3674380, upload-time = "2026-08-19T05:03:19.158Z" },
{ url = "https://files.pythonhosted.org/packages/ee/a4/55eb54507073089ab27743c5da2113c84f0d0b1715b33175fdd943c9652d/lxml-6.1.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d506bdba580ecb1a6ad2e2b5c49445e66d3e1f95894885739094393a1aad237", size = 8602111, upload-time = "2026-08-19T04:58:28.017Z" }, { url = "https://files.pythonhosted.org/packages/ee/a4/55eb54507073089ab27743c5da2113c84f0d0b1715b33175fdd943c9652d/lxml-6.1.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d506bdba580ecb1a6ad2e2b5c49445e66d3e1f95894885739094393a1aad237", size = 8602111, upload-time = "2026-08-19T04:58:28.017Z" },
{ url = "https://files.pythonhosted.org/packages/bc/bf/6332f45d78da385bb01d5cac3fe4acda19f025d1307cbc7ad538355fecbb/lxml-6.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12acd337d2821cb8b9247dfe4b7aa2f2769a3df5ae8511b7e550df42b8f4d3c3", size = 4638376, upload-time = "2026-08-19T04:58:41.181Z" }, { url = "https://files.pythonhosted.org/packages/bc/bf/6332f45d78da385bb01d5cac3fe4acda19f025d1307cbc7ad538355fecbb/lxml-6.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12acd337d2821cb8b9247dfe4b7aa2f2769a3df5ae8511b7e550df42b8f4d3c3", size = 4638376, upload-time = "2026-08-19T04:58:41.181Z" },
{ url = "https://files.pythonhosted.org/packages/68/e0/21fba0fe74d417fbe976903ae6bc77e92cdce01aae7b636abd87756f4588/lxml-6.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5078ff51e6316c0f75ea8127c2cd24374747fb351f62fb93d1761f8ae5a04a40", size = 4939689, upload-time = "2026-08-19T04:58:48.526Z" }, { url = "https://files.pythonhosted.org/packages/68/e0/21fba0fe74d417fbe976903ae6bc77e92cdce01aae7b636abd87756f4588/lxml-6.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5078ff51e6316c0f75ea8127c2cd24374747fb351f62fb93d1761f8ae5a04a40", size = 4939689, upload-time = "2026-08-19T04:58:48.526Z" },
@@ -762,6 +852,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/18/35fb14dd6baccbffa6daeb2369802f04a94e3f73db3c7bb405dbab009729/lxml-6.1.2-cp315-cp315t-win32.whl", hash = "sha256:87534cec6ea325435e4adf2326b0cf3110eee9a47abf73652eb155db639c08c6", size = 3901151, upload-time = "2026-08-19T05:04:58.671Z" }, { url = "https://files.pythonhosted.org/packages/20/18/35fb14dd6baccbffa6daeb2369802f04a94e3f73db3c7bb405dbab009729/lxml-6.1.2-cp315-cp315t-win32.whl", hash = "sha256:87534cec6ea325435e4adf2326b0cf3110eee9a47abf73652eb155db639c08c6", size = 3901151, upload-time = "2026-08-19T05:04:58.671Z" },
{ url = "https://files.pythonhosted.org/packages/f0/b6/07530896ca062bc3d2f09d5cb8a48e799c05b12c496205db03159ba13b6c/lxml-6.1.2-cp315-cp315t-win_amd64.whl", hash = "sha256:4e220a9c297e5d36895d489a08c9a3f1f6193b6414e702c5fb751e4a3767f8d0", size = 4395355, upload-time = "2026-08-19T05:05:01.651Z" }, { url = "https://files.pythonhosted.org/packages/f0/b6/07530896ca062bc3d2f09d5cb8a48e799c05b12c496205db03159ba13b6c/lxml-6.1.2-cp315-cp315t-win_amd64.whl", hash = "sha256:4e220a9c297e5d36895d489a08c9a3f1f6193b6414e702c5fb751e4a3767f8d0", size = 4395355, upload-time = "2026-08-19T05:05:01.651Z" },
{ url = "https://files.pythonhosted.org/packages/31/a8/237d8de1d77085cfd41d0c6049a044d8d01886f3afb7f1eda2f43d900a96/lxml-6.1.2-cp315-cp315t-win_arm64.whl", hash = "sha256:f16a407766bac51c65d605b06d900821751a79aa20e12185f273f14a17180e7b", size = 3822823, upload-time = "2026-08-19T05:05:04.63Z" }, { url = "https://files.pythonhosted.org/packages/31/a8/237d8de1d77085cfd41d0c6049a044d8d01886f3afb7f1eda2f43d900a96/lxml-6.1.2-cp315-cp315t-win_arm64.whl", hash = "sha256:f16a407766bac51c65d605b06d900821751a79aa20e12185f273f14a17180e7b", size = 3822823, upload-time = "2026-08-19T05:05:04.63Z" },
{ url = "https://files.pythonhosted.org/packages/b9/c9/11bfea1b3afc7a27ce74222b2e12b97005f3b81aa0011313769a14afd60a/lxml-6.1.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4622c5616683faf63791b349e6c8dad7717412dc5f29f4febe7575f110609a86", size = 3942892, upload-time = "2026-08-19T05:03:21.567Z" },
{ url = "https://files.pythonhosted.org/packages/c4/98/9885a4505758885c113af2bc2335a9fced99cb01e07e42895a62f1eb97fb/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:733dfb492ec3dfef8350a5cc896e90d202c5171e791e1609e77563751d69a15d", size = 4213061, upload-time = "2026-08-19T05:03:24.259Z" },
{ url = "https://files.pythonhosted.org/packages/2c/5e/e80d9e7d6e54b0693df60c7eeeed4aa19e2e3936dadf0676e6a3e8ac1ee1/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4618b20f43dc98b49569b1dc822176140ea0f2598d672a6989187ba49bcbfec1", size = 4322013, upload-time = "2026-08-19T05:03:26.764Z" },
{ url = "https://files.pythonhosted.org/packages/52/22/2e896cfba4e86b805eb8a3259cbdc1601971dc8fda5b1db2044ec2a3e6f0/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f93bc5e25992f5545709000d840c6cafdbd022781a7a0ed79d58a5633733a4e8", size = 4257333, upload-time = "2026-08-19T05:03:29.355Z" },
{ url = "https://files.pythonhosted.org/packages/b0/1d/9dbdbfa284ea96aee7c368e0ac73994f7e1375281070c355bcd85d4f7a77/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:662432a6103e671d971e06e75ed146d9ff67f39d2c98c2f26613b6057f54eafc", size = 4410828, upload-time = "2026-08-19T05:03:31.948Z" },
{ url = "https://files.pythonhosted.org/packages/f9/f2/fea24b044219458c252e0a0a08074a27dc9e28edb85f83533e36e3ddb57d/lxml-6.1.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ba0dfead73be5be9ad0b7fbf9f31ff29c1b1eae858816dfc8d85099d6e4af0d6", size = 3511278, upload-time = "2026-08-19T05:03:34.597Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -894,6 +990,21 @@ dependencies = [
] ]
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
{ url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
{ url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
{ url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
{ url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
{ url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
{ url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
{ url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
{ url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
{ url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
{ url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
{ url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
{ url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
{ url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
{ url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
{ url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
{ url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
{ url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
@@ -954,10 +1065,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
{ url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
{ url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
{ url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
{ url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
{ url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
{ url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
{ url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
{ url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
{ url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
{ url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
{ url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
{ url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
{ url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
{ url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
{ url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
{ url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
] ]
[[package]] [[package]]
@@ -1017,7 +1140,7 @@ name = "pytest-cov"
version = "7.1.0" version = "7.1.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "coverage" }, { name = "coverage", extra = ["toml"] },
{ name = "pluggy" }, { name = "pluggy" },
{ name = "pytest" }, { name = "pytest" },
] ]
@@ -1062,6 +1185,22 @@ version = "2026.7.19"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" },
{ url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" },
{ url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" },
{ url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804, upload-time = "2026-07-19T00:16:44.662Z" },
{ url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723, upload-time = "2026-07-19T00:16:46.412Z" },
{ url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932, upload-time = "2026-07-19T00:16:47.956Z" },
{ url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407, upload-time = "2026-07-19T00:16:49.43Z" },
{ url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448, upload-time = "2026-07-19T00:16:51.352Z" },
{ url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297, upload-time = "2026-07-19T00:16:53.071Z" },
{ url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736, upload-time = "2026-07-19T00:16:54.607Z" },
{ url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298, upload-time = "2026-07-19T00:16:56.289Z" },
{ url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430, upload-time = "2026-07-19T00:16:57.933Z" },
{ url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683, upload-time = "2026-07-19T00:16:59.583Z" },
{ url = "https://files.pythonhosted.org/packages/b5/f4/7532a2c59d56f5398902c20de60f0c9a5d1cd364e42a051b48e1b210be7b/regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d", size = 266778, upload-time = "2026-07-19T00:17:01.032Z" },
{ url = "https://files.pythonhosted.org/packages/83/2b/cf1bc631db154eb95520d9d5dbc2371ff77a0f014bbf7d748fed8496aa63/regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965", size = 277983, upload-time = "2026-07-19T00:17:02.571Z" },
{ url = "https://files.pythonhosted.org/packages/8d/bd/56ceaf170e875d5a6761bf2bfd0d040f1cacc896850d5e40cb29b11bbd06/regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e", size = 276961, upload-time = "2026-07-19T00:17:04.135Z" },
{ url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" }, { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" },
{ url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" }, { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" },
{ url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" }, { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" },
@@ -1150,6 +1289,15 @@ version = "0.4.11"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/39/6c/aec38dfee314a38cb7c0940fe055b22f22627b3e0a216772c24372eef3a9/selectolax-0.4.11.tar.gz", hash = "sha256:2b565ddabce6c9a7b73fa28a39acf8f411a084fa2f169234ec2470f552d4421d", size = 4883455, upload-time = "2026-07-15T07:25:30.588Z" } sdist = { url = "https://files.pythonhosted.org/packages/39/6c/aec38dfee314a38cb7c0940fe055b22f22627b3e0a216772c24372eef3a9/selectolax-0.4.11.tar.gz", hash = "sha256:2b565ddabce6c9a7b73fa28a39acf8f411a084fa2f169234ec2470f552d4421d", size = 4883455, upload-time = "2026-07-15T07:25:30.588Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/ac/aeb509fbeaccf339ef66c7ee9e3a203c908e5fffb53cb4deb5aa29a16a41/selectolax-0.4.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:80af1c7345701934769679a83fcc86526c56eef590423cc2d55cc1eb81adfa5f", size = 2223554, upload-time = "2026-07-15T07:23:56.383Z" },
{ url = "https://files.pythonhosted.org/packages/66/5a/804248c189b0eadeeee613dddfaf965d1476cb7e6480222c2ea707d7d89d/selectolax-0.4.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a532f1993f08f627300891751982dd9641ce1bd29618b535232aec9fa023a1c8", size = 2279032, upload-time = "2026-07-15T07:23:58.056Z" },
{ url = "https://files.pythonhosted.org/packages/75/68/058eb65781e25c25d5db2eed4a26f0a8a63251c012def5e20eab1ec11eac/selectolax-0.4.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8166bb8cad8f2eabed664f689b708851078b74dd50dd93e426b291095badc2cb", size = 2358093, upload-time = "2026-07-15T07:23:59.722Z" },
{ url = "https://files.pythonhosted.org/packages/15/42/2150e058273f5afa3669026bef89bf16cd7d76b38adad4f0a537fec34c2c/selectolax-0.4.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad4bbf08af4e2c89f78dd12a7b8265f0924d0434705bd955b5a262297a924452", size = 2409067, upload-time = "2026-07-15T07:24:01.361Z" },
{ url = "https://files.pythonhosted.org/packages/5d/07/24287ec819f8f8c5ccdcf39b3672fd569a6f53acf7c3ed167dd829fc0f70/selectolax-0.4.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:81f21972788df01b83a5940d9b7d62e6e222f80872c2cfaf67927276e8ef4975", size = 2362638, upload-time = "2026-07-15T07:24:02.921Z" },
{ url = "https://files.pythonhosted.org/packages/7d/64/7b5be0d6a53b9be7f0548c5e54fb2ca9b6c59552fc95ae9ac3d7bccd44ae/selectolax-0.4.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9862c1d0435e89e419e0c595084eaa8f6f71c3fb7a968bdff51db595cec07161", size = 2417494, upload-time = "2026-07-15T07:24:04.465Z" },
{ url = "https://files.pythonhosted.org/packages/2b/2a/07c4a7421bb3d547a54bdd11e91a5e6c5fcf0423bf0f58716fa04db9a53c/selectolax-0.4.11-cp311-cp311-win32.whl", hash = "sha256:e2d7e1a2b67a5e8d251733b2c3f978ed26a56d04552472e0a2fb5ea41fa4e663", size = 1767180, upload-time = "2026-07-15T07:24:05.998Z" },
{ url = "https://files.pythonhosted.org/packages/ac/17/6516a608f7d0f258b27ae7ca838a4db2fb72ade6fed21a05b566795d44cf/selectolax-0.4.11-cp311-cp311-win_amd64.whl", hash = "sha256:8da39a07a589fd181b5e8d25f695d7d40d3a1d89e47c2e00c08ece7fff5ddd3d", size = 1884122, upload-time = "2026-07-15T07:24:07.511Z" },
{ url = "https://files.pythonhosted.org/packages/08/28/3d31a7b6aaad9df1c874278d859a8bae025ec26dced1cf5cc12214901151/selectolax-0.4.11-cp311-cp311-win_arm64.whl", hash = "sha256:dcd24bfc4899e4df2bdcbabc973384367686f391e5a2d8ebb229c043f43be82a", size = 1824236, upload-time = "2026-07-15T07:24:08.998Z" },
{ url = "https://files.pythonhosted.org/packages/d7/96/d3b085e0a6bcb1e9a21a62617a826f14c7d569f70fee848579039db276bf/selectolax-0.4.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25d633cddca0fc769e7d890e9e838908fb4a7326eec5e3b23ae42c27f457541d", size = 2251530, upload-time = "2026-07-15T07:24:10.677Z" }, { url = "https://files.pythonhosted.org/packages/d7/96/d3b085e0a6bcb1e9a21a62617a826f14c7d569f70fee848579039db276bf/selectolax-0.4.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25d633cddca0fc769e7d890e9e838908fb4a7326eec5e3b23ae42c27f457541d", size = 2251530, upload-time = "2026-07-15T07:24:10.677Z" },
{ url = "https://files.pythonhosted.org/packages/a7/65/21ff78e6050b71f6467e7baad3eb58b935ade210a72e3e339ce9e6f68ac5/selectolax-0.4.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:634db1b7ae1b1f10ee0b0adbef0eee1aec65a3d01f09b75132bc5b043b7623d1", size = 2300794, upload-time = "2026-07-15T07:24:12.322Z" }, { url = "https://files.pythonhosted.org/packages/a7/65/21ff78e6050b71f6467e7baad3eb58b935ade210a72e3e339ce9e6f68ac5/selectolax-0.4.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:634db1b7ae1b1f10ee0b0adbef0eee1aec65a3d01f09b75132bc5b043b7623d1", size = 2300794, upload-time = "2026-07-15T07:24:12.322Z" },
{ url = "https://files.pythonhosted.org/packages/a4/60/faa1878ba9bd362e9078f664e570d3085b3cd679aee49c044a03b4530513/selectolax-0.4.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:168b34466dc4f998d7ceb04f218693546d141543e7c5d327f9e006c0799cd62c", size = 2382882, upload-time = "2026-07-15T07:24:13.906Z" }, { url = "https://files.pythonhosted.org/packages/a4/60/faa1878ba9bd362e9078f664e570d3085b3cd679aee49c044a03b4530513/selectolax-0.4.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:168b34466dc4f998d7ceb04f218693546d141543e7c5d327f9e006c0799cd62c", size = 2382882, upload-time = "2026-07-15T07:24:13.906Z" },
@@ -1225,6 +1373,13 @@ dependencies = [
] ]
sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637, upload-time = "2026-08-11T19:07:09.829Z" } sdist = { url = "https://files.pythonhosted.org/packages/3b/21/77b4c147963073040dc3c3a5cb7a8c3001a1893c0209432cb77f9df836aa/sqlalchemy-2.0.52.tar.gz", hash = "sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97", size = 9945637, upload-time = "2026-08-11T19:07:09.829Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/08/cc5f7627b92f1456bc0b5fb7e98af4600248abe422a44da0d17a3fe6a448/sqlalchemy-2.0.52-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0c3ce43907374889f3352bdcc6195c970148a2cb71574cd0237a5071a37fb6c", size = 2172460, upload-time = "2026-08-11T20:58:22.429Z" },
{ url = "https://files.pythonhosted.org/packages/ed/dc/9a2abad8bfc8fdcd38c64adc056aeefab7aaa96ecd32f5e8c140e6375f17/sqlalchemy-2.0.52-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a0d48c4b80717c61385b4e966e087c839a66cfd7b780641dcb428f4dba65608", size = 3355720, upload-time = "2026-08-11T21:00:06.746Z" },
{ url = "https://files.pythonhosted.org/packages/a8/73/e75597b5841043e3c74055d00d4feb53d9a49a5c89ba2450d2d9aab53597/sqlalchemy-2.0.52-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938325a5373267afc53bfbe72983b20fbd64ca47842aac62433c3da1137ecff1", size = 3354394, upload-time = "2026-08-11T21:05:51.454Z" },
{ url = "https://files.pythonhosted.org/packages/12/25/410fbc6c2f1fa8310f4ef1b6847d47d0ac1c042c7b4e81eaaca063d030a9/sqlalchemy-2.0.52-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8438a98d49424acf69d0d53c0a522951dfe49a6f2d86417fbb37ad3066ab43", size = 3306991, upload-time = "2026-08-11T21:00:08.603Z" },
{ url = "https://files.pythonhosted.org/packages/b2/ba/25ffd5c24681ea4b46e62c80ceca8200ce204de1773366321306cf3f608a/sqlalchemy-2.0.52-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4699dbb8d396d199e7e78fd4d525e3ad3d6008a9c8c0160b87e74c606c2c3736", size = 3327454, upload-time = "2026-08-11T21:05:53.368Z" },
{ url = "https://files.pythonhosted.org/packages/c2/f1/0f1b1d4800e51218e736a06ed55a3b2a59c257600bbaca7673bf13d2dbec/sqlalchemy-2.0.52-cp311-cp311-win32.whl", hash = "sha256:cef328349452ae152637df4d11ce5a0919ecdf0a363e16c830c3518ee33bde72", size = 2131248, upload-time = "2026-08-11T21:09:50.765Z" },
{ url = "https://files.pythonhosted.org/packages/7a/f0/04d2ac5ad66f3d31278f37064ed5f5ef3fe653f7bdaa67036663f223d186/sqlalchemy-2.0.52-cp311-cp311-win_amd64.whl", hash = "sha256:f1c850792a3b25a3ad74dade3f05e4f402cdebfea27438bcadafaa1617f77bcc", size = 2156943, upload-time = "2026-08-11T21:09:51.979Z" },
{ url = "https://files.pythonhosted.org/packages/e0/d5/1b77a026d161f98a08f11af1a5f6c47b98ee7c7e2648af525a1004826c78/sqlalchemy-2.0.52-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727", size = 2170940, upload-time = "2026-08-11T20:58:11.25Z" }, { url = "https://files.pythonhosted.org/packages/e0/d5/1b77a026d161f98a08f11af1a5f6c47b98ee7c7e2648af525a1004826c78/sqlalchemy-2.0.52-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727", size = 2170940, upload-time = "2026-08-11T20:58:11.25Z" },
{ url = "https://files.pythonhosted.org/packages/54/bd/f444444adb37b5d53753fb1730ee7a421628e2e3b756c4da461af7e6394a/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee", size = 3383415, upload-time = "2026-08-11T21:02:38.534Z" }, { url = "https://files.pythonhosted.org/packages/54/bd/f444444adb37b5d53753fb1730ee7a421628e2e3b756c4da461af7e6394a/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee", size = 3383415, upload-time = "2026-08-11T21:02:38.534Z" },
{ url = "https://files.pythonhosted.org/packages/be/57/2eadf93a552568c57e8680b7e58bb5e9770d80942a1bdbaf4f2f63f0d7c8/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b", size = 3398577, upload-time = "2026-08-11T21:16:59.092Z" }, { url = "https://files.pythonhosted.org/packages/be/57/2eadf93a552568c57e8680b7e58bb5e9770d80942a1bdbaf4f2f63f0d7c8/sqlalchemy-2.0.52-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b", size = 3398577, upload-time = "2026-08-11T21:16:59.092Z" },
@@ -1281,6 +1436,60 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743, upload-time = "2026-03-06T23:50:32.465Z" }, { url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743, upload-time = "2026-03-06T23:50:32.465Z" },
] ]
[[package]]
name = "tomli"
version = "2.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
{ url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
{ url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
{ url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
{ url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
{ url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
{ url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
{ url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
{ url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
{ url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
{ url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
{ url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
{ url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
{ url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
{ url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
{ url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
{ url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
{ url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
{ url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
{ url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
{ url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
{ url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
{ url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
{ url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
{ url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
{ url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
]
[[package]] [[package]]
name = "trafilatura" name = "trafilatura"
version = "2.2.0" version = "2.2.0"