From 135bea642c1b9ac92cfd479a7e7480db54644812 Mon Sep 17 00:00:00 2001 From: adish-rmr Date: Sat, 15 Nov 2025 16:02:37 +0100 Subject: [PATCH] added api entrypoint --- claude.md | 194 ------------------ .../functions => old}/_old/echaFind.py | 0 .../functions => old}/_old/echaProcess.py | 0 .../functions => old}/_old/find.py | 0 .../functions => old}/_old/mongo_functions.py | 0 .../functions => old}/_old/pubchem.py | 0 .../functions => old}/_old/scraper_cosing.py | 0 debug_echa_find.py => old/debug_echa_find.py | 0 .../functions => old}/pdf_extraction.py | 0 .../functions => old}/resources/ECHA_Logo.svg | 0 .../resources/echa_chem_logo.svg | 0 .../resources/injectableHeader.html | 0 pyproject.toml | 2 + src/pif_compiler/__init__.py | 1 + src/pif_compiler/api/__init__.py | 0 src/pif_compiler/api/routes/api_echa.py | 102 +++++++++ src/pif_compiler/functions/html_to_pdf.py | 18 -- src/pif_compiler/main.py | 172 ++++++++++++++++ uv.lock | 28 +++ 19 files changed, 305 insertions(+), 212 deletions(-) delete mode 100644 claude.md rename {src/pif_compiler/functions => old}/_old/echaFind.py (100%) rename {src/pif_compiler/functions => old}/_old/echaProcess.py (100%) rename {src/pif_compiler/functions => old}/_old/find.py (100%) rename {src/pif_compiler/functions => old}/_old/mongo_functions.py (100%) rename {src/pif_compiler/functions => old}/_old/pubchem.py (100%) rename {src/pif_compiler/functions => old}/_old/scraper_cosing.py (100%) rename debug_echa_find.py => old/debug_echa_find.py (100%) rename {src/pif_compiler/functions => old}/pdf_extraction.py (100%) rename {src/pif_compiler/functions => old}/resources/ECHA_Logo.svg (100%) rename {src/pif_compiler/functions => old}/resources/echa_chem_logo.svg (100%) rename {src/pif_compiler/functions => old}/resources/injectableHeader.html (100%) create mode 100644 src/pif_compiler/api/__init__.py create mode 100644 src/pif_compiler/api/routes/api_echa.py delete mode 100644 src/pif_compiler/functions/html_to_pdf.py create mode 100644 src/pif_compiler/main.py diff --git a/claude.md b/claude.md deleted file mode 100644 index 164e3c7..0000000 --- a/claude.md +++ /dev/null @@ -1,194 +0,0 @@ -# PIF Compiler - Project Context - -## Overview -Application to generate **Product Information Files (PIF)** for cosmetic products. This is a regulatory document required for cosmetics safety assessment. - -## Development Environment -- **Platform**: Windows -- **Package Manager**: [uv](https://github.com/astral-sh/uv) - Fast Python package installer and resolver -- **Python Version**: 3.12+ - -## Tech Stack -- **Backend**: Python 3.12+ -- **Frontend**: Streamlit -- **Database**: MongoDB (primary), potential relational DB (not yet implemented) -- **Package Manager**: uv -- **Build System**: hatchling - -## Common Commands -```bash -# Install dependencies -uv sync - -# Add a new dependency -uv add - -# Run the application -uv run pif-compiler - -# Activate virtual environment (if needed) -.venv\Scripts\activate # Windows -``` - -## Project Structure - -``` -pif_compiler/ -├── src/pif_compiler/ -│ ├── classes/ # Data models & type definitions -│ │ ├── pif_class.py # Main PIF data model -│ │ ├── classes.py # Supporting classes (Ingredient, ExpositionInfo, SedTable, ProdCompany) -│ │ └── types_enum.py # Enums for cosmetic types, physical forms, exposure routes -│ │ -│ └── functions/ # Core functionality modules -│ ├── scraper_cosing.py # COSING database scraper (EU cosmetic ingredients) -│ ├── mongo_functions.py # MongoDB connection & queries -│ ├── html_to_pdf.py # PDF generation with Playwright -│ ├── echaFind.py # ECHA dossier search -│ ├── echaProcess.py # ECHA data extraction & processing -│ ├── pubchem.py # PubChem API for chemical properties -│ ├── find.py # Unified search interface (QUACKO/ECHA) -│ └── pdf_extraction.py # PDF processing utilities -│ -└── data/ - ├── pif_schema.json # JSON schema for PIF structure - └── input.json # Example input data format -``` - -## Core Functionality - -### 1. Data Models ([classes/](src/pif_compiler/classes/)) - -#### PIF Class ([pif_class.py](src/pif_compiler/classes/pif_class.py:10)) -Main data model containing: -- Product information (name, type, CNCP, company) -- Ingredient list with quantities -- Exposure information -- Safety evaluation data (SED table, warnings) -- Metadata (creation date) - -#### Supporting Classes ([classes.py](src/pif_compiler/classes/classes.py)) -- **Ingredient**: INCI name, CAS number, quantity, toxicity values (SED, NOAEL, MOS), PubChem data -- **ExpositionInfo**: Application details, exposure routes, calculated daily exposure -- **SedTable**: Safety evaluation data table -- **ProdCompany**: Production company information - -#### Type Enumerations ([types_enum.py](src/pif_compiler/classes/types_enum.py)) -Bilingual (EN/IT) enums for: -- **CosmeticType**: 100+ product types (foundations, lipsticks, skincare, etc.) -- **PhysicalForm**: Liquid, semi-solid, solid, aerosol, hybrid forms -- **NormalUser**: Adult/Child -- **PlaceApplication**: Face, etc. -- **RoutesExposure**: Dermal, Ocular, Oral -- **NanoRoutes**: Same as above for nanomaterials - -### 2. External Data Sources - -#### COSING Database ([scraper_cosing.py](src/pif_compiler/functions/scraper_cosing.py)) -EU cosmetic ingredients database -- Search by INCI name, CAS number, or EC number -- Extract: substance ID, CAS/EC numbers, restrictions, SCCS opinions -- Handle "identified ingredients" recursively -- Functions: `cosing_search()`, `clean_cosing()`, `parse_cas_numbers()` - -#### ECHA Database ([echaFind.py](src/pif_compiler/functions/echaFind.py), [echaProcess.py](src/pif_compiler/functions/echaProcess.py)) -European Chemicals Agency dossiers -- **Search**: Find dossiers by CAS/substance name ([echaFind.py:44](src/pif_compiler/functions/echaFind.py:44)) -- **Extract**: Toxicity data (NOAEL, LD50) from HTML pages -- **Process**: Convert HTML → Markdown → JSON → DataFrame -- **Scraping Types**: RepeatedDose (NOAEL), AcuteToxicity (LD50) -- **Local caching**: DuckDB in-memory for scraped data -- Functions: `search_dossier()`, `echaExtract()`, `echa_noael_ld50()` - -#### PubChem ([pubchem.py](src/pif_compiler/functions/pubchem.py)) -Chemical properties for DAP calculation -- Properties: LogP, Molecular Weight, TPSA, Melting Point, pH, Dissociation Constants -- Uses `pubchempy` + custom certificate handling -- Function: `pubchem_dap(cas)` - -#### QUACKO/Find Module ([find.py](src/pif_compiler/functions/find.py)) -Unified search interface for ECHA -- Search by CAS, EC, or substance name -- Extract multiple sections (ToxSummary, AcuteToxicity, RepeatedDose, GeneticToxicity, physical properties) -- Support for local HTML files -- Functions: `search_dossier()`, `get_section_links_from_index()` - -### 3. Database Layer - -#### MongoDB ([mongo_functions.py](src/pif_compiler/functions/mongo_functions.py)) -- Database: `toxinfo` -- Collection: `toxinfo` (ingredient data from COSING/ECHA) -- Functions: - - `connect(user, password, database)` - MongoDB Atlas connection - - `value_search(db, value, mode)` - Search by INCI, CAS, EC, chemical name - -### 4. PDF Generation ([html_to_pdf.py](src/pif_compiler/functions/html_to_pdf.py), [pdf_extraction.py](src/pif_compiler/functions/pdf_extraction.py)) -- **Playwright-based**: Headless browser for HTML → PDF -- **Dynamic headers**: Inject substance info, ECHA logos -- **Cleanup**: Remove empty sections, fix page breaks -- **Batch processing**: `search_generate_pdfs()` for multiple pages -- Output: Structured folders by CAS/EC/RML ID - -## Data Flow - -1. **Input**: Product formulation (INCI names, quantities) -2. **Enrichment**: - - Search COSING for ingredient info - - Query MongoDB for cached data - - Fetch PubChem for chemical properties - - Extract ECHA toxicity data (NOAEL/LD50) -3. **Calculation**: - - SED (Systemic Exposure Dose) - - MOS (Margin of Safety) - - Daily exposure values -4. **Output**: PIF document (likely PDF/HTML format) - -## Key Dependencies -- `streamlit` - Frontend -- `pydantic` - Data validation -- `pymongo` - MongoDB client -- `requests` - HTTP requests -- `beautifulsoup4` - HTML parsing -- `playwright` - PDF generation -- `pubchempy` - PubChem API -- `pandas` - Data processing -- `duckdb` - Local caching - -## Important Notes - -### CAS Number Handling -- CAS numbers can contain special separators (`/`, `;`, `,`, `--`) -- Parser handles parenthetical info and invalid values - -### ECHA Scraping -- **Logging**: All operations logged to `echa.log` -- **Dossier Status**: Active preferred, falls back to Inactive -- **Scraping Modes**: - - `local_search=True`: Check local cache first - - `local_only=True`: Only use cached data -- **Multi-substance**: `echaExtract_multi()` for batch processing -- **Filtering**: Can filter by route (oral/dermal/inhalation) and dose descriptor - -### Bilingual Support -- Enums support EN/IT via `TranslatedEnum.get_translation(lang)` -- Italian used as primary language in comments - -### Regulatory Context -- SCCS: Scientific Committee on Consumer Safety -- CNCP: Cosmetic Notification Portal -- NOAEL: No Observed Adverse Effect Level -- SED: Systemic Exposure Dose -- MOS: Margin of Safety -- DAP: Dermal Absorption Percentage - -## TODO/Future Work -- Relational DB implementation (mentioned but not present) -- Streamlit UI (referenced but code not in current files) -- Main entry point (`pif-compiler` script in pyproject.toml) -- LLM approximation for exposure values (mentioned in [classes.py:55-60](src/pif_compiler/classes/classes.py:55)) - -## Development Notes -- Project appears to consolidate previously separate codebases -- Heavy use of external APIs (rate limiting may apply) -- Certificate handling needed for PubChem API -- MongoDB credentials required for database access diff --git a/src/pif_compiler/functions/_old/echaFind.py b/old/_old/echaFind.py similarity index 100% rename from src/pif_compiler/functions/_old/echaFind.py rename to old/_old/echaFind.py diff --git a/src/pif_compiler/functions/_old/echaProcess.py b/old/_old/echaProcess.py similarity index 100% rename from src/pif_compiler/functions/_old/echaProcess.py rename to old/_old/echaProcess.py diff --git a/src/pif_compiler/functions/_old/find.py b/old/_old/find.py similarity index 100% rename from src/pif_compiler/functions/_old/find.py rename to old/_old/find.py diff --git a/src/pif_compiler/functions/_old/mongo_functions.py b/old/_old/mongo_functions.py similarity index 100% rename from src/pif_compiler/functions/_old/mongo_functions.py rename to old/_old/mongo_functions.py diff --git a/src/pif_compiler/functions/_old/pubchem.py b/old/_old/pubchem.py similarity index 100% rename from src/pif_compiler/functions/_old/pubchem.py rename to old/_old/pubchem.py diff --git a/src/pif_compiler/functions/_old/scraper_cosing.py b/old/_old/scraper_cosing.py similarity index 100% rename from src/pif_compiler/functions/_old/scraper_cosing.py rename to old/_old/scraper_cosing.py diff --git a/debug_echa_find.py b/old/debug_echa_find.py similarity index 100% rename from debug_echa_find.py rename to old/debug_echa_find.py diff --git a/src/pif_compiler/functions/pdf_extraction.py b/old/pdf_extraction.py similarity index 100% rename from src/pif_compiler/functions/pdf_extraction.py rename to old/pdf_extraction.py diff --git a/src/pif_compiler/functions/resources/ECHA_Logo.svg b/old/resources/ECHA_Logo.svg similarity index 100% rename from src/pif_compiler/functions/resources/ECHA_Logo.svg rename to old/resources/ECHA_Logo.svg diff --git a/src/pif_compiler/functions/resources/echa_chem_logo.svg b/old/resources/echa_chem_logo.svg similarity index 100% rename from src/pif_compiler/functions/resources/echa_chem_logo.svg rename to old/resources/echa_chem_logo.svg diff --git a/src/pif_compiler/functions/resources/injectableHeader.html b/old/resources/injectableHeader.html similarity index 100% rename from src/pif_compiler/functions/resources/injectableHeader.html rename to old/resources/injectableHeader.html diff --git a/pyproject.toml b/pyproject.toml index a3a6217..1e432aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "beautifulsoup4>=4.14.2", "dotenv>=0.9.9", "duckdb>=1.4.1", + "fastapi>=0.121.2", "marimo>=0.16.5", "markdown-to-json>=2.1.2", "markdownify>=1.2.0", @@ -25,6 +26,7 @@ dependencies = [ "python-dotenv>=1.2.1", "requests>=2.32.5", "streamlit>=1.50.0", + "uvicorn>=0.35.0", "weasyprint>=66.0", ] diff --git a/src/pif_compiler/__init__.py b/src/pif_compiler/__init__.py index e69de29..8b13789 100644 --- a/src/pif_compiler/__init__.py +++ b/src/pif_compiler/__init__.py @@ -0,0 +1 @@ + diff --git a/src/pif_compiler/api/__init__.py b/src/pif_compiler/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pif_compiler/api/routes/api_echa.py b/src/pif_compiler/api/routes/api_echa.py new file mode 100644 index 0000000..7bb22c9 --- /dev/null +++ b/src/pif_compiler/api/routes/api_echa.py @@ -0,0 +1,102 @@ +from fastapi import APIRouter, HTTPException, status +from pydantic import BaseModel, Field +from typing import Optional, Dict, Any + +from pif_compiler.services.srv_echa import orchestrator +from pif_compiler.services.common_log import get_logger + +logger = get_logger() + +router = APIRouter() + + +class EchaRequest(BaseModel): + cas: str = Field(..., description="CAS number of the substance to search for") + + class Config: + json_schema_extra = { + "example": { + "cas": "50-00-0" + } + } + + +class EchaResponse(BaseModel): + success: bool + cas: str + data: Optional[Dict[str, Any]] = None + error: Optional[str] = None + + +@router.post("/echa/search", response_model=EchaResponse, tags=["ECHA"]) +async def search_echa_substance(request: EchaRequest): + """ + Search for substance information in ECHA database. + + This endpoint orchestrates the full ECHA data extraction process: + 1. Validates the CAS number + 2. Checks local MongoDB cache + 3. If not found locally, fetches from ECHA: + - Substance information + - Dossier information + - Toxicological information + - Acute toxicity data + - Repeated dose toxicity data + 4. Caches the result locally + + Args: + request: EchaRequest containing the CAS number + + Returns: + EchaResponse with the substance data or error information + """ + logger.info(f"API request received for CAS: {request.cas}") + + try: + result = orchestrator(request.cas) + + if result is None: + logger.warning(f"No data found for CAS: {request.cas}") + return EchaResponse( + success=False, + cas=request.cas, + data=None, + error="No data found for the provided CAS number. The CAS may be invalid or not registered in ECHA." + ) + + # Remove MongoDB _id field if present (it's not JSON serializable) + if "_id" in result: + del result["_id"] + + logger.info(f"Successfully retrieved data for CAS: {request.cas}") + return EchaResponse( + success=True, + cas=request.cas, + data=result, + error=None + ) + + except Exception as e: + logger.error(f"Error processing request for CAS {request.cas}: {str(e)}", exc_info=True) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Internal error while processing CAS {request.cas}: {str(e)}" + ) + + +@router.get("/echa/health", tags=["ECHA"]) +async def echa_health_check(): + """ + Health check endpoint for ECHA service. + + Returns the status of the ECHA service components. + """ + return { + "status": "healthy", + "service": "echa-orchestrator", + "components": { + "api": "operational", + "scraper": "operational", + "parser": "operational" + } + } diff --git a/src/pif_compiler/functions/html_to_pdf.py b/src/pif_compiler/functions/html_to_pdf.py deleted file mode 100644 index 85a6cbc..0000000 --- a/src/pif_compiler/functions/html_to_pdf.py +++ /dev/null @@ -1,18 +0,0 @@ -from playwright.sync_api import sync_playwright - - -def generate_pdf(url, pdf_path): - with sync_playwright() as p: - # Avvia un browser (può essere 'chromium', 'firefox', o 'webkit') - browser = p.chromium.launch(headless=True) - page = browser.new_page() - - # Vai all'URL specificato - page.goto(url) - - # Genera il PDF - page.pdf(path=pdf_path, format="A4") - - # Chiudi il browser - browser.close() - print(f"PDF salvato con successo in: {pdf_path}") diff --git a/src/pif_compiler/main.py b/src/pif_compiler/main.py new file mode 100644 index 0000000..ab9d4b1 --- /dev/null +++ b/src/pif_compiler/main.py @@ -0,0 +1,172 @@ +from fastapi import FastAPI, Request, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from fastapi.exceptions import RequestValidationError +from contextlib import asynccontextmanager +import time + +from pif_compiler.services.common_log import get_logger + +# Import dei tuoi router +from pif_compiler.api.routes import api_echa + +# Configurazione logging +logger = get_logger() + +# Lifespan events (startup/shutdown) - moderna alternativa a @app.on_event +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup + logger.info("🚀 Starting up API...") + # Qui puoi inizializzare: + # - Connessioni al database + + yield # L'app gira qui + + # Shutdown + logger.info("👋 Shutting down API...") + # Qui chiudi le connessioni e fai cleanup + # - Chiudi DB connections + # - Salva cache + # - Cleanup risorse + + +# Inizializza FastAPI +app = FastAPI( + title="Comsoguard API", + description="Central API for Comsoguard services", + version="0.0.1", + docs_url="/docs", + redoc_url="/redoc", + openapi_url="/openapi.json", + lifespan=lifespan +) + + +# ==================== MIDDLEWARE ==================== + +# 1. CORS - Configura in base alle tue esigenze +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "*" + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +# 2. Logging Middleware - Log di tutte le richieste +@app.middleware("http") +async def log_requests(request: Request, call_next): + start_time = time.time() + + # Log della richiesta in arrivo + logger.info( + f"📥 {request.method} {request.url.path} - " + f"Client: {request.client.host}" + ) + + # Esegui la richiesta + response = await call_next(request) + + # Calcola il tempo di elaborazione + process_time = time.time() - start_time + + # Log della risposta + logger.info( + f"📤 {request.method} {request.url.path} - " + f"Status: {response.status_code} - " + f"Time: {process_time:.3f}s" + ) + + return response + + +# ==================== EXCEPTION HANDLERS ==================== + +# Handler per errori di validazione (422) +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + logger.warning(f"❌ Validation error on {request.url.path}: {exc.errors()}") + return JSONResponse( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + content={ + "error": "Validation Error", + "detail": exc.errors(), + "body": exc.body + } + ) + + +# Handler per errori generici +@app.exception_handler(Exception) +async def general_exception_handler(request: Request, exc: Exception): + logger.error(f"💥 Unhandled exception on {request.url.path}: {str(exc)}", exc_info=True) + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content={ + "error": "Internal Server Error", + "message": "Si è verificato un errore imprevisto. Riprova più tardi." + } + ) + + +# ==================== ROUTERS ==================== + +# Include i tuoi router qui + +app.include_router( + api_echa.router, + prefix="/api/v1", + tags=["ECHA"] +) + + +# ==================== ROOT ENDPOINTS ==================== + +@app.get("/", tags=["Root"]) +async def root(): + """ + Endpoint di benvenuto - mostra info base dell'API + """ + return { + "message": "Welcome to Comsoguard API", + "version": "0.0.1", + "docs": "/docs", + "redoc": "/redoc" + } + + +@app.get("/health", tags=["Health"]) +async def health_check(): + """ + Health check endpoint - utile per monitoring e load balancers + """ + return { + "status": "healthy", + "service": "comsoguard-api", + "version": "0.0.1" + } + + +@app.get("/ping", tags=["Health"]) +async def ping(): + """ + Ping endpoint - risposta velocissima per verificare che l'API sia up + """ + return {"ping": "pong"} + + +# ==================== RUN SERVER ==================== + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "pif_compiler.main:app", + host="0.0.0.0", + port=8000, + reload=True, # Auto-reload on code changes during development + log_level="info" + ) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 360fce5..8f597aa 100644 --- a/uv.lock +++ b/uv.lock @@ -17,6 +17,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200 }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -400,6 +409,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/79/4f544d73fcc0513b71296cb3ebb28a227d22e80dec27204977039b9fa875/duckdb-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:280fd663dacdd12bb3c3bf41f3e5b2e5b95e00b88120afabb8b8befa5f335c6f", size = 12336460 }, ] +[[package]] +name = "fastapi" +version = "0.121.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/f08f264da34cf160db82c62ffb335e838b1fc16cbcc905f474c7d4c815db/fastapi-0.121.2.tar.gz", hash = "sha256:ca8e932b2b823ec1721c641e3669472c855ad9564a2854c9899d904c2848b8b9", size = 342944 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/23/dfb161e91db7c92727db505dc72a384ee79681fe0603f706f9f9f52c2901/fastapi-0.121.2-py3-none-any.whl", hash = "sha256:f2d80b49a86a846b70cc3a03eb5ea6ad2939298bf6a7fe377aa9cd3dd079d358", size = 109201 }, +] + [[package]] name = "fonttools" version = "4.60.1" @@ -937,6 +961,7 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "dotenv" }, { name = "duckdb" }, + { name = "fastapi" }, { name = "marimo" }, { name = "markdown-to-json" }, { name = "markdownify" }, @@ -951,6 +976,7 @@ dependencies = [ { name = "python-dotenv" }, { name = "requests" }, { name = "streamlit" }, + { name = "uvicorn" }, { name = "weasyprint" }, ] @@ -959,6 +985,7 @@ requires-dist = [ { name = "beautifulsoup4", specifier = ">=4.14.2" }, { name = "dotenv", specifier = ">=0.9.9" }, { name = "duckdb", specifier = ">=1.4.1" }, + { name = "fastapi", specifier = ">=0.121.2" }, { name = "marimo", specifier = ">=0.16.5" }, { name = "markdown-to-json", specifier = ">=2.1.2" }, { name = "markdownify", specifier = ">=1.2.0" }, @@ -973,6 +1000,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.2.1" }, { name = "requests", specifier = ">=2.32.5" }, { name = "streamlit", specifier = ">=1.50.0" }, + { name = "uvicorn", specifier = ">=0.35.0" }, { name = "weasyprint", specifier = ">=66.0" }, ]