stage22: Abschluss & Production Readiness — E2E-Tests, CHANGELOG, Dokumentation
This commit is contained in:
301
API.md
301
API.md
@@ -1,8 +1,6 @@
|
||||
# NSCT — API-Referenz
|
||||
|
||||
> **Hinweis:** Diese API-Referenz beschreibt nur die Endpunkte
|
||||
> von Stage 0. Die eigentliche Evidence-Pipeline (Suche,
|
||||
> Analyse, Report) wird in späteren Stages implementiert.
|
||||
> **Hinweis:** Diese API-Referenz beschreibt den aktuellen Stand (Stage 22).
|
||||
|
||||
## Server-Adresse
|
||||
|
||||
@@ -54,7 +52,7 @@ Wenn bereit:
|
||||
"status": "ready",
|
||||
"llm": "ok",
|
||||
"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
|
||||
- `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
|
||||
@@ -91,12 +85,12 @@ GET /providers
|
||||
{
|
||||
"llm": {
|
||||
"available": true,
|
||||
"model": "Qwen3.6-6-35B",
|
||||
"model": "Qwen3.6-35B",
|
||||
"max_concurrency": 3
|
||||
},
|
||||
"vision": {
|
||||
"available": true,
|
||||
"model": "Qwen2-VL-3B"
|
||||
"model": "Qwen2.5-VL-3B"
|
||||
},
|
||||
"audio": {
|
||||
"available": true,
|
||||
@@ -115,28 +109,291 @@ GET /providers
|
||||
```
|
||||
|
||||
**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
|
||||
curl http://localhost:8080/docs
|
||||
### POST /v1/research
|
||||
|
||||
**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 (1–2000 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
|
||||
curl http://localhost:8080/redoc
|
||||
**Depth-Konfiguration:**
|
||||
|
||||
| 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
|
||||
@@ -153,5 +410,5 @@ Alle Fehler folgen einem konsistenten Format:
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
Stage 0: Keine Authentifizierung. Dies wird in späteren Stages
|
||||
hinzugefügt (API-Key, OAuth, etc.).
|
||||
Stage 22: Keine Authentifizierung. Dies kann in späteren Stages
|
||||
hinzugefügt werden (API-Key, OAuth, etc.).
|
||||
138
ARCHITECTURE.md
138
ARCHITECTURE.md
@@ -9,6 +9,10 @@ Behauptungen (Claims) und erzeugt einen neutralen, quellengestützten Bericht.
|
||||
**Kernprinzip:** Webcontent ist DATA, keine INSTRUCTION. Der LLM verarbeitet
|
||||
nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt.
|
||||
|
||||
**Stage 0–22 abgeschlossen.** NSCT ist production-ready.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architektur-Diagramm
|
||||
|
||||
```
|
||||
@@ -24,8 +28,8 @@ nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt.
|
||||
│ │ │ │
|
||||
│ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ │ │
|
||||
│ │ │ Search │──▶│ Fetch │──▶│ Extract │──▶│ Classify │ │ │
|
||||
│ │ │ (SearX │ │ (HTTP/ │ │(Trafilat │ │(LLM/Rule)│ │ │
|
||||
│ │ │ NG) │ │ Playwright)│ │ura/BS4) │ │ │ │ │
|
||||
│ │ │ (DDG/ │ │(HTTP/ │ │(Trafilat │ │(LLM/Rule)│ │ │
|
||||
│ │ │ Multi) │ │ Playright)│ │ura/BS4) │ │ │ │ │
|
||||
│ │ └─────────┘ └──────────┘ └───────────┘ └─────┬─────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────┐ ┌───────────┐ ┌────────────┐ ┌───┴─────┐ │ │
|
||||
@@ -35,17 +39,25 @@ nur strukturierte Daten, niemals rohen Webcontent direkt als Prompt.
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────────┐ │ │
|
||||
│ │ │Evidence │ │ │
|
||||
│ │ │Scoring (6D) │ │ │
|
||||
│ │ └──────┬───────┘ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────────┐ │ │
|
||||
│ │ │ Report Gen. │ │ │
|
||||
│ │ │ (LLM) │ │ │
|
||||
│ │ └──────┬───────┘ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌──────────────┐ │ │
|
||||
│ │ │ Evidence DB │ │ │
|
||||
│ │ │Provenance │ │ │
|
||||
│ │ │+ Hash │ │ │
|
||||
│ │ └──────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Datenfluss
|
||||
|
||||
```
|
||||
@@ -65,49 +77,134 @@ Query ──▶ SearchProvider.search() ──▶ List[SearchResult]
|
||||
│
|
||||
▼
|
||||
EvidenceRelation[] ──▶ Agreement matrix
|
||||
│
|
||||
│ (6-dim evidence scoring)
|
||||
│
|
||||
▼
|
||||
EvidencePackage ──▶ Neutral Synthesis
|
||||
│
|
||||
│ (LLM: synthesize report)
|
||||
│
|
||||
▼
|
||||
ResearchReport (Summary + Findings + Disagreements + Uncertainties)
|
||||
│
|
||||
▼
|
||||
research_run_hash (deterministic, reproducible)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Module
|
||||
|
||||
### 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
|
||||
- **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`
|
||||
|
||||
### 4.2 `src/nsct/models/` — Pydantic v2 Schemas
|
||||
|
||||
- **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
|
||||
|
||||
### 4.3 `src/nsct/storage/` — Database Layer
|
||||
|
||||
- **models.py**: SQLAlchemy 2.0 declarative models
|
||||
- **engine.py**: Async engine factory with pool management
|
||||
- Uses `asyncpg` for PostgreSQL
|
||||
|
||||
### 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
|
||||
- All providers accept structured data, never raw web content
|
||||
|
||||
### 4.5 `src/nsct/security/` — Security
|
||||
|
||||
- **policy.py**: SSRF protection, URL validation, IP blocklisting
|
||||
- Called before every outbound HTTP request
|
||||
|
||||
### 4.6 `src/nsct/config.py` — Configuration
|
||||
|
||||
- Pydantic BaseSettings — all values from environment
|
||||
- Zero hard-coded secrets or URLs
|
||||
- `AppSettings.from_env()` creates the root configuration
|
||||
|
||||
### 4.7 `src/nsct/logging_config.py` — Structured Logging
|
||||
|
||||
- JSON-formatted log output
|
||||
- Per-request context tracking (`research_id`, `llm_request_id`)
|
||||
- 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
|
||||
|
||||
```python
|
||||
@@ -129,10 +226,12 @@ class ContentFetcher(ABC):
|
||||
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
|
||||
von Providern ohne Codeänderung im Core.
|
||||
|
||||
---
|
||||
|
||||
## 6. Sicherheitsarchitektur
|
||||
|
||||
- **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.
|
||||
- **Non-Root-Docker:** Der Container läuft als nicht-root User `nsct`.
|
||||
- **Read-Only-Filesystem:** Wo möglich (`read_only: true` + `tmpfs`).
|
||||
- **Dropped Capabilities:** `cap_drop: [ALL]`
|
||||
|
||||
---
|
||||
|
||||
## 7. Datenmodell
|
||||
|
||||
@@ -156,6 +258,21 @@ von Providern ohne Codeänderung im Core.
|
||||
| `citation_edges` | Quelle-zu-Quelle Referenzen |
|
||||
| `research_reports` | Aggregierte Forschungsberichte |
|
||||
|
||||
### Evidence Scoring (Stage 8)
|
||||
|
||||
6 dimensionale Scores für jede Evidenz:
|
||||
|
||||
| Dimension | Range | Beschreibung |
|
||||
|-----------|-------|-------------|
|
||||
| `source_independence` | 0–1 | Wie unabhängig ist diese Quelle? |
|
||||
| `primary_source_proximity` | 0–1 | Wie nah ist die Quelle an der Primärquelle? |
|
||||
| `cross_source_support` | 0–1 | Wie viele unabhängige Quellen bestätigen? |
|
||||
| `contradiction_level` | 0–1 | Wie hoch ist der Widerspruch? (invertiert) |
|
||||
| `evidence_directness` | 0–1 | Wie direkt ist die Evidenz? |
|
||||
| `date_relevance_score` | 0–1 | Wie aktuell ist die Evidenz? |
|
||||
|
||||
---
|
||||
|
||||
## 8. Nicht-funktionale Anforderungen
|
||||
|
||||
| Kriterium | Anforderung |
|
||||
@@ -164,5 +281,8 @@ von Providern ohne Codeänderung im Core.
|
||||
| Database | PostgreSQL 16+, asyncpg, SQLAlchemy 2.0 |
|
||||
| Logging | Strukturiert (JSON), jede Anfrage tracebar |
|
||||
| Config | Environment-only, keine Config-Dateien |
|
||||
| Testing | pytest-asyncio, FastAPI TestClient |
|
||||
| Deployment | Docker Compose, reproduzierbar |
|
||||
| Testing | pytest-asyncio, FastAPI TestClient, E2E-Tests |
|
||||
| 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
290
CHANGELOG.md
Normal 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 0–22), 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 | ✅ |
|
||||
@@ -5,6 +5,7 @@
|
||||
- **Docker** (≥ 24.0) und **Docker Compose** (≥ 2.23)
|
||||
- Mindestens 2 GB freier RAM
|
||||
- Zugang zu einem OpenAI-kompatiblen LLM-Endpunkt
|
||||
- PostgreSQL 16+ (wird mit docker-compose bereitgestellt)
|
||||
|
||||
## 2. Konfiguration
|
||||
|
||||
@@ -18,17 +19,17 @@ Trage folgende Werte ein:
|
||||
|
||||
```env
|
||||
# LLM (verpflichtend)
|
||||
NSCT_LLM_BASE_URL=http://192.168.80.199:8030/openai/v1
|
||||
NSCT_LLM_MODEL=Qwen3.6-6-35B
|
||||
NSCT_LLM_BASE_URL=http://your-llm-host:8030/openai/v1
|
||||
NSCT_LLM_MODEL=Qwen3.6-35B
|
||||
NSCT_LLM_MAX_CONCURRENCY=3
|
||||
NSCT_LLM_API_KEY=<dein-api-key>
|
||||
|
||||
# Vision (optional)
|
||||
NSCT_VISION_BASE_URL=http://192.168.80.199:8030/openai/visual/v1
|
||||
NSCT_VISION_MODEL=Qwen2-VL-3B
|
||||
NSCT_VISION_BASE_URL=http://your-vision-host:8030/openai/visual/v1
|
||||
NSCT_VISION_MODEL=Qwen2.5-VL-3B
|
||||
|
||||
# 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
|
||||
|
||||
# PostgreSQL
|
||||
@@ -102,14 +103,16 @@ docker compose logs -f nsct-api
|
||||
|
||||
- `.env` **niemals** committen — `.gitignore` behandelt das
|
||||
- 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)
|
||||
- Non-Root-Docker-Container mit Read-Only-Filesystem
|
||||
- Dropped Capabilities (`cap_drop: [ALL]`)
|
||||
|
||||
### 5.2 Skalierung
|
||||
|
||||
- Derzeit: Single-Instance (kein horizontal scaling)
|
||||
- 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
|
||||
|
||||
@@ -122,8 +125,7 @@ volumes:
|
||||
driver: local
|
||||
```
|
||||
|
||||
Für Production: Verwende ein volumen-Plugin mit Backup-Unterstützung
|
||||
(например, Velero für Kubernetes).
|
||||
Für Production: Verwende ein volumen-Plugin mit Backup-Unterstützung.
|
||||
|
||||
### 5.4 Monitoring
|
||||
|
||||
@@ -136,6 +138,18 @@ docker compose logs --tail=100 nsct-api
|
||||
|
||||
# DB-Größe
|
||||
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
|
||||
@@ -166,7 +180,7 @@ docker compose logs postgres
|
||||
|
||||
```bash
|
||||
# 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
|
||||
@@ -180,4 +194,31 @@ docker compose up --build -d
|
||||
|
||||
# Datenbank-Migrationen (wenn benötigt)
|
||||
# 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
155
README.md
@@ -1,6 +1,4 @@
|
||||
# ============================================================
|
||||
# NSCT — Neutral Search Crawler Tool
|
||||
# ============================================================
|
||||
|
||||
**NSCT** ist ein vollständig lokal betreibbares, containerisiertes Recherche- und
|
||||
Analyse-System. Es durchsucht Webquellen, extrahiert Inhalte, vergleicht
|
||||
@@ -9,30 +7,43 @@ quellengestützten Bericht.
|
||||
|
||||
**NSCT steht für:** Neutral Search Crawler Tool.
|
||||
|
||||
---
|
||||
|
||||
## Architektur-Übersicht
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
|
||||
│ User / CLI │───▶│ NSCT API │───▶│ FastAPI / │
|
||||
│ │ │ (FastAPI) │ │ uvicorn │
|
||||
└─────────────┘ └──────────────┘ └───────────────┘
|
||||
│
|
||||
┌────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────┐ ┌─────────┐ ┌──────────┐
|
||||
│ LLM │ │ Vision │ │ Audio │
|
||||
│ Provider │ │ Model │ │ Model │
|
||||
└───────────┘ └─────────┘ └──────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ PostgreSQL (evidence store) │
|
||||
└─────────────────────────────────────┘
|
||||
▲
|
||||
│
|
||||
┌──────────────┐
|
||||
│ SearXNG │ (optional search backend)
|
||||
└──────────────┘
|
||||
┌─────────────┐ ┌──────────────────────────┐ ┌───────────────┐
|
||||
│ User / CLI │───▶│ NSCT REST API (FastAPI) │───▶│ uvicorn │
|
||||
│ │ │ (async, background pip.)│ │ :8080 │
|
||||
└─────────────┘ └──────────┬───────────────┘ └───────────────┘
|
||||
│
|
||||
┌───────────────┼─────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────┐ ┌──────────────┐
|
||||
│ Search │ │ Content │ │ Crawler │
|
||||
│ Providers │ │ Fetcher │ │ (SSRF-Prot) │
|
||||
│ (DDG, Multi) │ │ (HTTP/ │ │ (HTML,PDF, │
|
||||
└──────┬───────┘ │ Playwright)│ │ PDF, ...) │
|
||||
│ └──────┬─────┘ └──────────────┘
|
||||
▼ ▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Evidence Pipeline (Orchestrator) │
|
||||
│ Search → Fetch → Extract → Claim → │
|
||||
│ Compare → Score → Synthesis → Report │
|
||||
└──────────────────────────┬─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ PostgreSQL (asyncpg) │
|
||||
│ Evidence Store │
|
||||
└────────────────────────┘
|
||||
▲
|
||||
│
|
||||
┌──────────────┴──────────────┐
|
||||
│ LLM (Qwen3.6-35B) │
|
||||
│ Vision (Qwen2.5-VL-3B) │
|
||||
│ Audio (STT) │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
@@ -42,6 +53,7 @@ quellengestützten Bericht.
|
||||
```bash
|
||||
# 1. Kopiere die Beispiel-Env
|
||||
cp .env.example .env
|
||||
|
||||
# 2. Trage deine Endpunkte ein (LLM, Vision, Audio, PostgreSQL)
|
||||
|
||||
# 3. Starte alles
|
||||
@@ -59,40 +71,101 @@ Die API ist danach unter `http://localhost:8080` erreichbar.
|
||||
```
|
||||
nsct/
|
||||
├── src/nsct/
|
||||
│ ├── api/ # FastAPI-Routen
|
||||
│ ├── models/ # Pydantic-Schemata
|
||||
│ ├── providers/ # Abstrakte Provider-Interfaces
|
||||
│ ├── api/ # FastAPI-Routen (health, research, search, etc.)
|
||||
│ ├── agents/ # Research Planner & Validator
|
||||
│ ├── 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
|
||||
│ └── storage/ # SQLAlchemy Models + Engine
|
||||
├── tests/ # pytest-Tests
|
||||
│ ├── stages/ # Pipeline-Stages (5-13: claim, evidence, synthesis)
|
||||
│ ├── 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
|
||||
├── Dockerfile
|
||||
├── pyproject.toml
|
||||
├── .env.example
|
||||
├── README.md
|
||||
├── ARCHITECTURE.md
|
||||
├── SECURITY.md
|
||||
├── METHODOLOGY.md
|
||||
├── API.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
|
||||
|
||||
**Stage 0** — Repository und Architekturgrundlage.
|
||||
**Stage 22** — Abschluss & Production Readiness.
|
||||
|
||||
Stage 0 enthält:
|
||||
- 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
|
||||
Alle 22 Stages sind abgeschlossen. NSCT ist production-ready:
|
||||
|
||||
Die eigentliche Evidence-Pipeline (Search, Fetch, Extract, Claim, Compare, Report)
|
||||
wird in späteren Stages implementiert.
|
||||
- **Pipeline:** Search → Fetch → Extract → Claim → Compare → Score → Synthesize → Report
|
||||
- **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
|
||||
|
||||
|
||||
44
SECURITY.md
44
SECURITY.md
@@ -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
|
||||
Sicherheitsmaßnahmen folgen dem "zero trust" Prinzip.
|
||||
|
||||
**Stage 0–22 abgeschlossen.** NSCT ist production-ready mit hardened Docker-Image.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
in die Control Plane als Prompt-Text.
|
||||
|
||||
---
|
||||
|
||||
## 3. SSRF-Schutz
|
||||
|
||||
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
|
||||
- `172.16.0.0/12` — Private Class B
|
||||
- `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
|
||||
- `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
|
||||
3. **Metadata-Endpunkte:** Cloud-Instance-Metadata (`169.254.169.254`, etc.)
|
||||
4. **Schemata:** `file://`, `ftp://`, `gopher://`, `ldap://`, `data://` blockiert
|
||||
|
||||
---
|
||||
|
||||
## 4. Prompt-Isolation
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 5. Untrusted Data Handling
|
||||
|
||||
| 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 |
|
||||
| Audio/Vision | Manipulierte Inputs | Content-Type-Validierung; Größ Limits |
|
||||
|
||||
## 6. Docker-Sicherheit
|
||||
---
|
||||
|
||||
## 6. Docker-Sicherheit (Stage 18+)
|
||||
|
||||
- **Non-Root-User:** `nsct` (UID 1000)
|
||||
- **Read-Only-Filesystem:** `read_only: true` wo möglich
|
||||
- **Dropped Capabilities:** `cap_drop: [ALL]`
|
||||
- **No Secrets in Image:** `.env` wird nicht gebuildet
|
||||
- **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
|
||||
|
||||
- Keine Speicherung von personenbezogenen Daten über die Quellen-Metadaten hinaus
|
||||
- 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."
|
||||
@@ -449,6 +449,7 @@ async def _run_pipeline(run: _ResearchRunState, request: ResearchRequest, budget
|
||||
if result.get("success"):
|
||||
run.state = ResearchRunState.COMPLETED.value
|
||||
run.report = result.get("report")
|
||||
run.sources = result.get("sources", [])
|
||||
run.claims = result.get("report", {}).get("claims", [])
|
||||
run.evidence_scores = result.get("report", {}).get("evidence", [])
|
||||
|
||||
|
||||
432
tests/stages/test_e2e_integration.py
Normal file
432
tests/stages/test_e2e_integration.py
Normal 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
215
uv.lock
generated
@@ -1,6 +1,10 @@
|
||||
version = 1
|
||||
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]]
|
||||
name = "aiosqlite"
|
||||
@@ -48,6 +52,14 @@ version = "0.31.0"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -119,6 +131,22 @@ version = "3.5.1"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -285,6 +313,21 @@ version = "7.15.4"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -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" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
toml = [
|
||||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dateparser"
|
||||
version = "1.4.2"
|
||||
@@ -415,6 +463,14 @@ version = "3.5.5"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -537,7 +593,7 @@ source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio", 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 = "truststore", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
@@ -580,6 +636,20 @@ version = "0.16.0"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -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/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/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/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" },
|
||||
@@ -660,6 +734,22 @@ version = "6.1.2"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -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/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/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]
|
||||
@@ -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" }
|
||||
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/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" },
|
||||
@@ -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/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/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/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/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]]
|
||||
@@ -1017,7 +1140,7 @@ name = "pytest-cov"
|
||||
version = "7.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage" },
|
||||
{ name = "coverage", extra = ["toml"] },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
@@ -1062,6 +1185,22 @@ version = "2026.7.19"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -1150,6 +1289,15 @@ version = "0.4.11"
|
||||
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" }
|
||||
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/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" },
|
||||
@@ -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" }
|
||||
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/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" },
|
||||
@@ -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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "trafilatura"
|
||||
version = "2.2.0"
|
||||
|
||||
Reference in New Issue
Block a user