Initial commit
All checks were successful
CI / Build and push Docker image (push) Successful in 1m29s
All checks were successful
CI / Build and push Docker image (push) Successful in 1m29s
This commit is contained in:
commit
6db4ea8e96
40 changed files with 3029 additions and 0 deletions
49
.forgejo/workflows/ci.yml
Normal file
49
.forgejo/workflows/ci.yml
Normal file
|
@ -0,0 +1,49 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and push Docker image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Forgejo Container Registry
|
||||
uses: https://github.com/docker/login-action@v3
|
||||
with:
|
||||
registry: git.tomfos.tr
|
||||
username: ${{ gitea.repository_owner }}
|
||||
password: ${{ secrets.FORGEJO_TOKEN }}
|
||||
|
||||
- name: Determine image metadata
|
||||
id: meta
|
||||
uses: https://github.com/docker/metadata-action@v5
|
||||
with:
|
||||
images: git.tomfos.tr/${{ gitea.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch,if=ref==refs/heads/main,format=latest
|
||||
type=ref,event=branch,format={{ref_name}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: https://github.com/docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=git.tomfos.tr/${{ gitea.repository }}:build-cache
|
||||
cache-to: type=registry,ref=git.tomfos.tr/${{ gitea.repository }}:build-cache,mode=max
|
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
47
Dockerfile
Normal file
47
Dockerfile
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Build stage using uv with a frozen lockfile and dependency caching
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS uv
|
||||
WORKDIR /app
|
||||
|
||||
# Enable bytecode compilation and copy mode
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_LINK_MODE=copy
|
||||
|
||||
# Install dependencies using the lockfile
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev --no-editable --no-install-project
|
||||
|
||||
# Install the project in a second layer
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev --no-editable
|
||||
|
||||
# Prepare runtime image
|
||||
FROM python:3.13-slim-bookworm AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Install minimal system dependencies and create runtime user
|
||||
RUN apt-get update && apt-get install -y curl \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& groupadd -g 1000 omcps \
|
||||
&& useradd -u 1000 -g 1000 -m omcps \
|
||||
&& mkdir -p /data \
|
||||
&& touch /data/memory.json \
|
||||
&& chown -R 1000:1000 /data
|
||||
|
||||
# Copy only the virtual environment from the build stage
|
||||
COPY --from=uv /app/.venv /app/.venv
|
||||
|
||||
# Switch to non-root user
|
||||
USER omcps
|
||||
|
||||
# Set environment variables for runtime
|
||||
ENV PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
MEMORY_FILE_PATH="/data/memory.json"
|
||||
|
||||
# Use wrapper script to handle startup
|
||||
ENTRYPOINT ["uvicorn", "openapi_mcp_server.server:create_app", "--factory", "--host", "0.0.0.0", "--port", "80"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost/health || exit 1
|
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 tom
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
68
README.md
Normal file
68
README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# OpenAPI MCP Server
|
||||
|
||||
A unified OpenAPI-compatible server for the Multi-agent Conversation Platform (MCP) that provides
|
||||
various tools for Open WebUI integration. This single server exposes multiple tools through FastAPI
|
||||
endpoints with full OpenAPI documentation.
|
||||
|
||||
## Features
|
||||
|
||||
The server provides the following tool endpoints:
|
||||
|
||||
- **[Web Content Parsing](docs/web.md)** (`/web`) - Extract and parse web content using trafilatura
|
||||
- **[Memory/Storage](docs/memory.md)** (`/memory`) - Structured knowledge graph memory system
|
||||
- **[Time Operations](docs/time.md)** (`/time`) - Time utilities and timezone operations
|
||||
- **[Weather Information](docs/weather.md)** (`/weather`) - Real-time weather data and forecasts
|
||||
- **[Search](docs/searxng.md)** (`/searxng`) - Web search through SearXNG instances
|
||||
|
||||
Each tool provides detailed endpoint documentation - click the links above to learn about specific
|
||||
endpoints and their capabilities.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using Docker Compose (Recommended)
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd openapi-mcp-server
|
||||
|
||||
# Start the server
|
||||
docker-compose up --build
|
||||
|
||||
# The server will be available at http://localhost:8080
|
||||
# OpenAPI documentation at http://localhost:8080/docs
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Install dependencies using uv
|
||||
uv sync
|
||||
|
||||
# Run the development server
|
||||
uv run openapi-mcp-server
|
||||
|
||||
# The server will start on the default port with auto-reload
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
|
||||
Once running, visit `/docs` for the interactive OpenAPI documentation where you can explore and test
|
||||
all available endpoints.
|
||||
|
||||
## Configuration
|
||||
|
||||
The server can be configured through environment variables:
|
||||
|
||||
- `MEMORY_FILE_PATH` - Path to the memory storage file (default: `/data/memory.json`)
|
||||
- Additional tool-specific configuration options are documented in the individual tool modules
|
||||
|
||||
## Architecture
|
||||
|
||||
This project follows a unified single-server architecture:
|
||||
|
||||
- **Single FastAPI application** serving multiple tool endpoints
|
||||
- **Modular tool design** with individual tools in `openapi_mcp_server/tools/`
|
||||
- **OpenAPI compliance** with full documentation and validation
|
||||
- **Docker-ready** with optimized multi-stage builds
|
||||
- **uv package management** for fast, reliable dependency handling
|
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
services:
|
||||
tools:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- MEMORY_FILE_PATH=/data/memory.json
|
||||
- SEARXNG_BASE_URL=http://searxng:8080
|
||||
image: git.tomfos.tr/tom/openapi-mcp-server:latest
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
ports:
|
||||
- "8080:80"
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data:/data:rw
|
46
docs/memory.md
Normal file
46
docs/memory.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Memory Tool
|
||||
|
||||
A structured knowledge graph memory system that provides persistent storage for entities,
|
||||
relationships, and observations.
|
||||
|
||||
## Overview
|
||||
|
||||
The Memory tool provides a comprehensive knowledge graph system within the OpenAPI MCP Server
|
||||
framework. It allows you to store entities (people, places, concepts), create relationships between
|
||||
them, and add observations or facts about each entity.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | `/memory/create_entities` | Store new entities (people, places, concepts) with their properties and observations. Each entity has a name, type, and list of observations. |
|
||||
| `POST` | `/memory/create_relations` | Connect entities with srelationships (works_at, lives_in, knows, etc). Creates typed relationships between existing entities. |
|
||||
| `POST` | `/memory/add_observations` | Add new facts or observations to entities you've already stored. Extends existing entity knowledge without duplication. |
|
||||
| `POST` | `/memory/delete_entities` | Remove entities and all their connections from memory. Cleans up both the entity and any relationships involving it. |
|
||||
| `POST` | `/memory/delete_observations` | Remove specific facts or observations from entities. Allows selective cleanup of entity information. |
|
||||
| `POST` | `/memory/delete_relations` | Remove specific relationships between entities. Maintains entities while removing their connections. |
|
||||
| `GET` | `/memory/read_graph` | Retrieve all stored entities and relationships from memory. Returns the complete knowledge graph. |
|
||||
| `POST` | `/memory/search_nodes` | Find entities by searching names, types, or observations. Performs fuzzy search across all entity data. |
|
||||
| `POST` | `/memory/open_nodes` | Retrieve specific entities and their connections by exact name. Returns requested entities with their relationships. |
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Knowledge Graph Structure**: Entities connected by typed relationships
|
||||
- **Flexible Entity Types**: Store people, places, concepts, or any custom entity types
|
||||
- **Rich Observations**: Each entity can have multiple observations or facts
|
||||
- **Relationship Management**: Create, query, and delete relationships between entities
|
||||
- **Search Capabilities**: Find entities by name, type, or observation content
|
||||
- **Persistent Storage**: All data persisted to file system with atomic operations
|
||||
- **Duplicate Prevention**: Automatic deduplication of entities and relationships
|
||||
|
||||
## Data Model
|
||||
|
||||
- **Entity**: Has name, entity_type, and list of observations
|
||||
- **Relationship**: Connects two entities with a relationship type (from, to, relation_type)
|
||||
- **Knowledge Graph**: Collection of entities and their relationships
|
||||
|
||||
## Usage
|
||||
|
||||
The Memory tool is automatically loaded by the OpenAPI MCP Server framework. All endpoints are
|
||||
available under the `/memory` prefix when the server is running. Visit `/docs` for interactive API
|
||||
documentation.
|
55
docs/searxng.md
Normal file
55
docs/searxng.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
# SearXNG Tool
|
||||
|
||||
A search proxy tool that provides search functionality through SearXNG search instances with OpenAPI
|
||||
compatibility.
|
||||
|
||||
## Overview
|
||||
|
||||
The SearXNG tool acts as a proxy server for SearXNG search instances, providing search capabilities
|
||||
within the OpenAPI MCP Server framework. It offers a standardized API interface for performing web
|
||||
searches across multiple search engines through SearXNG.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | `/searxng/search` | Search the web across multiple search engines. Accepts query, categories, engines, language, format, and pagination parameters. Returns structured search results with titles, URLs, content snippets, and metadata. |
|
||||
| `GET` | `/searxng/categories` | Get available search categories (general, images, news, etc). Returns list of categories that can be used to filter search results. |
|
||||
| `GET` | `/searxng/engines` | Get list of available search engines (Google, Bing, DuckDuckGo, etc). Returns engines that can be specified for targeted searches. |
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Multi-Engine Search**: Search across multiple search engines simultaneously
|
||||
- **Category Filtering**: Filter results by category (general, images, videos, news, etc.)
|
||||
- **Engine Selection**: Choose specific search engines for targeted results
|
||||
- **Structured Results**: Returns clean, structured search results with titles, URLs, and snippets
|
||||
- **Metadata Rich**: Includes search metadata like result count, suggestions, and engine info
|
||||
- **Pagination Support**: Navigate through multiple pages of search results
|
||||
- **Language Support**: Search in specific languages with language parameter
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure the target SearXNG instance using the `SEARXNG_BASE_URL` environment variable:
|
||||
|
||||
- **Default**: `http://localhost:8080`
|
||||
- **Custom**: Set `SEARXNG_BASE_URL=https://your-searxng-instance.com`
|
||||
|
||||
## Search Parameters
|
||||
|
||||
- **query**: Search terms (required)
|
||||
- **categories**: Comma-separated category filters (optional)
|
||||
- **engines**: Comma-separated engine selection (optional)
|
||||
- **language**: Language code (default: "en")
|
||||
- **format**: Response format (default: "json")
|
||||
- **pageno**: Page number for pagination (default: 1)
|
||||
|
||||
## Usage
|
||||
|
||||
The SearXNG tool is automatically loaded by the OpenAPI MCP Server framework. All endpoints are
|
||||
available under the `/searxng` prefix when the server is running. Visit `/docs` for interactive API
|
||||
documentation.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Requires a running SearXNG instance. SearXNG is a free, open-source metasearch engine that aggregates
|
||||
results from multiple search engines without tracking users.
|
34
docs/time.md
Normal file
34
docs/time.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Time Tool
|
||||
|
||||
A comprehensive time utilities tool providing secure time operations, timezone conversion, and time
|
||||
calculations.
|
||||
|
||||
## Overview
|
||||
|
||||
The Time tool provides robust time-related functionality for the OpenAPI MCP Server, including
|
||||
current time retrieval, timezone conversions, Unix timestamp conversion, and timestamp parsing.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/time/get_time` | Get current time in any IANA timezone (defaults to UTC). Optional `timezone` parameter accepts IANA timezone names like 'America/New_York', 'Europe/London', etc. |
|
||||
| `POST` | `/time/unix_to_iso` | Convert Unix epoch timestamp to ISO format. Accepts Unix timestamp (float) and optional target timezone. |
|
||||
| `POST` | `/time/convert_time` | Convert timestamp from one timezone to another. Requires timestamp, source timezone, and target timezone. |
|
||||
| `POST` | `/time/elapsed_time` | Calculate time difference between two timestamps. Returns difference in specified units (seconds, minutes, hours, days). |
|
||||
| `POST` | `/time/parse_timestamp` | Parse flexible human-readable timestamps (e.g. 'June 1st 2024 3:30 PM', 'tomorrow at noon', '2024-06-01 15:30') into standardized UTC ISO format. |
|
||||
| `GET` | `/time/list_time_zones` | Get list of all valid IANA timezone names for use with other endpoints. |
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Flexible Input**: Supports various timestamp formats and human-readable date strings
|
||||
- **Timezone Aware**: Full IANA timezone support with automatic conversions
|
||||
- **Unix Compatibility**: Convert Unix epoch timestamps to readable ISO format
|
||||
- **Time Calculations**: Calculate elapsed time between any two timestamps
|
||||
- **Standardized Output**: All timestamps returned in ISO 8601 format
|
||||
|
||||
## Usage
|
||||
|
||||
The Time tool is automatically loaded by the OpenAPI MCP Server framework. All endpoints are
|
||||
available under the `/time` prefix when the server is running. Visit `/docs` for interactive API
|
||||
documentation.
|
44
docs/weather.md
Normal file
44
docs/weather.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Weather Tool
|
||||
|
||||
A weather information tool that provides real-time weather data and forecasts using the Open-Meteo API.
|
||||
|
||||
## Overview
|
||||
|
||||
The Weather tool provides weather information services within the OpenAPI MCP Server framework.
|
||||
It offers current weather conditions and hourly forecasts based on geographic coordinates using the
|
||||
reliable Open-Meteo weather API.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/weather/forecast` | Get current weather conditions and hourly forecast by coordinates. Requires latitude and longitude parameters. Temperature unit (Celsius/Fahrenheit) is automatically determined based on location. |
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Real-time Weather**: Current temperature and wind speed conditions
|
||||
- **Hourly Forecasts**: Detailed hourly weather data including temperature, humidity, and wind speed
|
||||
- **Smart Units**: Automatic temperature unit selection (Fahrenheit for US/Liberia/Myanmar, Celsius elsewhere)
|
||||
- **Geographic Accuracy**: Location-based weather using precise latitude/longitude coordinates
|
||||
- **Timezone Aware**: Weather data includes proper timezone information
|
||||
- **Reliable Source**: Uses Open-Meteo API for accurate, up-to-date weather information
|
||||
|
||||
## Data Structure
|
||||
|
||||
Weather responses include:
|
||||
|
||||
- **Current conditions**: Real-time temperature and wind speed
|
||||
- **Hourly forecasts**: Temperature, relative humidity, and wind speed by hour
|
||||
- **Location metadata**: Coordinates, timezone, and regional information
|
||||
- **Units**: Clear indication of temperature and measurement units
|
||||
|
||||
## Usage
|
||||
|
||||
The Weather tool is automatically loaded by the OpenAPI MCP Server framework. The forecast endpoint is
|
||||
available at `/weather/forecast` and requires latitude and longitude query parameters. Visit `/docs`
|
||||
for interactive API documentation.
|
||||
|
||||
## Data Source
|
||||
|
||||
Powered by the Open-Meteo API (<https://open-meteo.com>), providing reliable and free weather data
|
||||
without requiring API keys.
|
34
docs/web.md
Normal file
34
docs/web.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Web Tool
|
||||
|
||||
A web content parsing tool that extracts and processes web content using trafilatura for clean text
|
||||
extraction.
|
||||
|
||||
## Overview
|
||||
|
||||
The Web tool provides web content parsing and extraction capabilities within the OpenAPI MCP Server
|
||||
framework. It can fetch web pages and extract clean, readable content from HTML.
|
||||
|
||||
## Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | `/web/web_read` | Extract and parse webpage content into clean markdown. Accepts URL and various formatting options (metadata, formatting, images, links, tables). Returns structured markdown content. |
|
||||
| `GET` | `/web/web_raw` | Fetch raw HTML content and headers from any URL. Returns unprocessed HTML content along with HTTP headers and status code for debugging or advanced processing. |
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Clean Content Extraction**: Uses trafilatura library for high-quality text extraction from web pages
|
||||
- **Markdown Output**: Converts web content to clean, readable markdown format
|
||||
- **Flexible Formatting**: Control inclusion of metadata, formatting, images, links, and tables
|
||||
- **Raw Content Access**: Get unprocessed HTML and headers when needed
|
||||
- **Error Handling**: Robust error handling for failed requests and invalid URLs
|
||||
|
||||
## Usage
|
||||
|
||||
The Web tool is automatically loaded by the OpenAPI MCP Server framework. All endpoints are
|
||||
available under the `/web` prefix when the server is running. Visit `/docs` for interactive API documentation.
|
||||
|
||||
## Technology
|
||||
|
||||
Built using trafilatura, a Python library specialized in extracting and processing web content,
|
||||
ensuring high-quality text extraction from web pages.
|
7
openapi_mcp_server/__init__.py
Normal file
7
openapi_mcp_server/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""OpenAPI MCP Server package."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__title__ = "OpenAPI MCP Server"
|
||||
__description__ = "An OpenAPI-compatible server for the Multi-agent Conversation Platform (MCP)."
|
25
openapi_mcp_server/__main__.py
Normal file
25
openapi_mcp_server/__main__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""Entry point for the OpenAPI MCP Server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uvicorn
|
||||
|
||||
from .core.config import config
|
||||
from .server import create_app
|
||||
|
||||
|
||||
def launch() -> None:
|
||||
"""Launch the OpenAPI MCP Server."""
|
||||
app = create_app()
|
||||
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=config.host,
|
||||
port=config.port,
|
||||
reload=config.reload,
|
||||
log_level="debug" if config.debug else "info",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
launch()
|
3
openapi_mcp_server/core/__init__.py
Normal file
3
openapi_mcp_server/core/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Core components for the OpenAPI MCP Server."""
|
||||
|
||||
from __future__ import annotations
|
29
openapi_mcp_server/core/config.py
Normal file
29
openapi_mcp_server/core/config.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
"""Configuration management for the OpenAPI MCP Server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class ServerConfig(BaseSettings):
|
||||
"""Server configuration settings."""
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
case_sensitive=False,
|
||||
)
|
||||
|
||||
host: str = Field(default="0.0.0.0", alias="HOST")
|
||||
port: int = Field(default=8000, alias="PORT")
|
||||
debug: bool = Field(default=False, alias="DEBUG")
|
||||
reload: bool = Field(default=False, alias="RELOAD")
|
||||
|
||||
# CORS settings
|
||||
cors_origins: list[str] = Field(default=["*"], alias="CORS_ORIGINS")
|
||||
cors_methods: list[str] = Field(default=["*"], alias="CORS_METHODS")
|
||||
cors_headers: list[str] = Field(default=["*"], alias="CORS_HEADERS")
|
||||
cors_credentials: bool = Field(default=True, alias="CORS_CREDENTIALS")
|
||||
|
||||
|
||||
config = ServerConfig()
|
23
openapi_mcp_server/core/middleware.py
Normal file
23
openapi_mcp_server/core/middleware.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Middleware setup for the FastAPI application."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from .config import config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import FastAPI
|
||||
|
||||
|
||||
def setup_middleware(app: FastAPI) -> None:
|
||||
"""Setup middleware for the FastAPI application."""
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=config.cors_origins,
|
||||
allow_credentials=config.cors_credentials,
|
||||
allow_methods=config.cors_methods,
|
||||
allow_headers=config.cors_headers,
|
||||
)
|
67
openapi_mcp_server/core/registry.py
Normal file
67
openapi_mcp_server/core/registry.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
"""Tool registry for dynamic discovery and registration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import FastAPI
|
||||
|
||||
from openapi_mcp_server.tools.base import BaseTool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ToolRegistry:
|
||||
"""Registry for managing tool modules and their routes."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the tool registry."""
|
||||
self.tools: dict[str, BaseTool] = {}
|
||||
|
||||
def discover_tools(self) -> None:
|
||||
"""Automatically discover and load all tool modules."""
|
||||
tools_path = Path(__file__).parent.parent / "tools"
|
||||
|
||||
for tool_dir in tools_path.iterdir():
|
||||
if (
|
||||
tool_dir.is_dir()
|
||||
and tool_dir.name not in {"__pycache__", "base.py"}
|
||||
and (tool_dir / "__init__.py").exists()
|
||||
and (tool_dir / "routes.py").exists()
|
||||
):
|
||||
self._load_tool(tool_dir.name)
|
||||
|
||||
def _load_tool(self, tool_name: str) -> None:
|
||||
"""Load a specific tool module."""
|
||||
try:
|
||||
# Import the tool's routes module
|
||||
module = importlib.import_module(f"openapi_mcp_server.tools.{tool_name}.routes")
|
||||
|
||||
# Look for a Tool class or get_tool function
|
||||
if hasattr(module, "tool"):
|
||||
tool = module.tool
|
||||
if isinstance(tool, BaseTool):
|
||||
self.tools[tool_name] = tool
|
||||
logger.info("Loaded tool: %s", tool_name)
|
||||
else:
|
||||
logger.warning("%s.tool is not a BaseTool instance", tool_name)
|
||||
else:
|
||||
logger.warning("%s module has no 'tool' attribute", tool_name)
|
||||
|
||||
except ImportError:
|
||||
logger.exception("Failed to load tool %s", tool_name)
|
||||
|
||||
def register_routes(self, app: FastAPI) -> None:
|
||||
"""Register all tool routes with the FastAPI application."""
|
||||
for tool_name, tool in self.tools.items():
|
||||
# Create a router with tool-specific prefix
|
||||
router = tool.get_router()
|
||||
app.include_router(router, prefix=f"/{tool_name}", tags=[tool_name])
|
||||
logger.info("Registered routes for tool: %s", tool_name)
|
||||
|
||||
|
||||
registry = ToolRegistry()
|
3
openapi_mcp_server/models/__init__.py
Normal file
3
openapi_mcp_server/models/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Shared data models for the OpenAPI MCP Server."""
|
||||
|
||||
from __future__ import annotations
|
18
openapi_mcp_server/models/base.py
Normal file
18
openapi_mcp_server/models/base.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""Base data models for the OpenAPI MCP Server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
"""Standard health check response."""
|
||||
|
||||
status: str = Field(default="healthy", description="Service health status")
|
||||
service: str = Field(..., description="Service name")
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error response."""
|
||||
|
||||
detail: str = Field(..., description="Error message")
|
64
openapi_mcp_server/server.py
Normal file
64
openapi_mcp_server/server.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
"""Main FastAPI application factory."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from . import __description__, __title__, __version__
|
||||
from .core.middleware import setup_middleware
|
||||
from .core.registry import registry
|
||||
from .models.base import HealthResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
|
||||
def generate_unique_id(route: APIRoute) -> str:
|
||||
"""Generate a unique ID for a route.
|
||||
|
||||
Args:
|
||||
route (APIRoute): The route to generate an ID for.
|
||||
|
||||
Returns:
|
||||
str: The unique ID for the route.
|
||||
"""
|
||||
if route.tags:
|
||||
return f"{route.tags[0]}_{route.name}"
|
||||
return route.name
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create and configure the FastAPI application.
|
||||
|
||||
Returns:
|
||||
FastAPI: Configured FastAPI application instance.
|
||||
"""
|
||||
app = FastAPI(
|
||||
title=__title__,
|
||||
version=__version__,
|
||||
description=__description__,
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
generate_unique_id_function=generate_unique_id,
|
||||
)
|
||||
|
||||
# Setup middleware
|
||||
setup_middleware(app)
|
||||
|
||||
# Add global health endpoint
|
||||
@app.get("/health", response_model=HealthResponse, tags=["health"])
|
||||
def health_check() -> HealthResponse:
|
||||
"""Global health check endpoint.
|
||||
|
||||
Returns:
|
||||
HealthResponse: Health status of the service.
|
||||
"""
|
||||
return HealthResponse(service="openapi-mcp-server")
|
||||
|
||||
# Discover and register tool routes
|
||||
registry.discover_tools()
|
||||
registry.register_routes(app)
|
||||
|
||||
return app
|
3
openapi_mcp_server/tools/__init__.py
Normal file
3
openapi_mcp_server/tools/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Tool implementations for the OpenAPI MCP Server."""
|
||||
|
||||
from __future__ import annotations
|
30
openapi_mcp_server/tools/base.py
Normal file
30
openapi_mcp_server/tools/base.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""Base classes for MCP tools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import APIRouter
|
||||
|
||||
|
||||
class BaseTool(ABC):
|
||||
"""Base class for all MCP tools."""
|
||||
|
||||
def __init__(self, name: str, description: str) -> None:
|
||||
"""Initialize the tool with name and description."""
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
@abstractmethod
|
||||
def get_router(self) -> APIRouter:
|
||||
"""Return the FastAPI router for this tool."""
|
||||
|
||||
def get_health_endpoint(self) -> dict[str, str]:
|
||||
"""Standard health check response for this tool.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: Health status dictionary.
|
||||
"""
|
||||
return {"status": "healthy", "service": self.name}
|
3
openapi_mcp_server/tools/memory/__init__.py
Normal file
3
openapi_mcp_server/tools/memory/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Memory/storage functionality tool."""
|
||||
|
||||
from __future__ import annotations
|
80
openapi_mcp_server/tools/memory/models.py
Normal file
80
openapi_mcp_server/tools/memory/models.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
"""Data models for memory/storage tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Memory(BaseModel):
|
||||
"""A timestamped fact that references one or more entities."""
|
||||
|
||||
id: str = Field(..., description="Unique identifier for this memory")
|
||||
content: str = Field(
|
||||
...,
|
||||
description=(
|
||||
"The detailed fact or information stored (comprehensive details for optimal recall)"
|
||||
),
|
||||
)
|
||||
entities: list[str] = Field(..., description="List of entity names this memory references")
|
||||
timestamp: str = Field(..., description="ISO timestamp when this memory was created")
|
||||
|
||||
|
||||
class Entity(BaseModel):
|
||||
"""Simple entity reference."""
|
||||
|
||||
name: str = Field(..., description="The name of the entity")
|
||||
entity_type: str = Field(default="general", description="The type of the entity")
|
||||
memory_count: int = Field(default=0, description="Number of memories referencing this entity")
|
||||
|
||||
|
||||
class MemoryGraph(BaseModel):
|
||||
"""Collection of memories and entities."""
|
||||
|
||||
memories: list[Memory]
|
||||
entities: list[Entity]
|
||||
|
||||
|
||||
class CreateMemoryRequest(BaseModel):
|
||||
"""Request to create a new memory."""
|
||||
|
||||
content: str = Field(
|
||||
...,
|
||||
description=(
|
||||
"The detailed fact or information to store. Include comprehensive details, "
|
||||
"context, and specifics for best recall - avoid brief summaries."
|
||||
),
|
||||
)
|
||||
entities: list[str] = Field(..., description="List of entity names this memory references")
|
||||
|
||||
|
||||
class SearchMemoryRequest(BaseModel):
|
||||
"""Request to search memories."""
|
||||
|
||||
query: str = Field(..., description="Search term to find in memory content or entity names")
|
||||
limit: int = Field(default=10, description="Maximum number of memories to return")
|
||||
|
||||
|
||||
class GetEntityRequest(BaseModel):
|
||||
"""Request to get memories for specific entities."""
|
||||
|
||||
entities: list[str] = Field(..., description="List of entity names to retrieve memories for")
|
||||
limit: int = Field(default=5, description="Maximum number of memories to return")
|
||||
|
||||
|
||||
class DeleteMemoryRequest(BaseModel):
|
||||
"""Request to delete specific memories."""
|
||||
|
||||
memory_ids: list[str] = Field(..., description="List of memory IDs to delete")
|
||||
|
||||
|
||||
class MemorySummary(BaseModel):
|
||||
"""Summary statistics about stored memories."""
|
||||
|
||||
total_memories: int = Field(..., description="Total number of memories stored")
|
||||
total_entities: int = Field(..., description="Total number of unique entities")
|
||||
oldest_memory: str | None = Field(..., description="ISO timestamp of oldest memory")
|
||||
latest_memory: str | None = Field(..., description="ISO timestamp of latest memory")
|
||||
memory_timespan_days: int | None = Field(
|
||||
..., description="Days between oldest and latest memory"
|
||||
)
|
||||
top_entities: list[Entity] = Field(..., description="Most frequently referenced entities")
|
295
openapi_mcp_server/tools/memory/routes.py
Normal file
295
openapi_mcp_server/tools/memory/routes.py
Normal file
|
@ -0,0 +1,295 @@
|
|||
"""API routes for memory/storage tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from openapi_mcp_server.tools.base import BaseTool
|
||||
|
||||
from .models import (
|
||||
CreateMemoryRequest,
|
||||
DeleteMemoryRequest,
|
||||
Entity,
|
||||
GetEntityRequest,
|
||||
Memory,
|
||||
MemoryGraph,
|
||||
MemorySummary,
|
||||
SearchMemoryRequest,
|
||||
)
|
||||
from .storage import (
|
||||
generate_memory_id,
|
||||
get_current_timestamp,
|
||||
read_memory_graph,
|
||||
save_memory_graph,
|
||||
)
|
||||
|
||||
|
||||
class MemoryTool(BaseTool):
|
||||
"""Simplified memory system for storing timestamped facts."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the memory tool."""
|
||||
super().__init__(
|
||||
name="memory",
|
||||
description="A simple memory system for storing timestamped facts about entities",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_memory(req: CreateMemoryRequest) -> Memory:
|
||||
"""Store a new memory/fact.
|
||||
|
||||
Returns:
|
||||
Memory: The newly created memory with auto-generated timestamp and ID.
|
||||
"""
|
||||
graph = read_memory_graph()
|
||||
|
||||
# Create new memory with auto-generated timestamp and ID
|
||||
memory = Memory(
|
||||
id=generate_memory_id(),
|
||||
content=req.content,
|
||||
entities=req.entities,
|
||||
timestamp=get_current_timestamp(),
|
||||
)
|
||||
|
||||
graph.memories.append(memory)
|
||||
|
||||
# Update entity counts and ensure entities exist
|
||||
entity_dict = {e.name: e for e in graph.entities}
|
||||
for entity_name in req.entities:
|
||||
if entity_name in entity_dict:
|
||||
entity_dict[entity_name].memory_count += 1
|
||||
else:
|
||||
# Create new entity
|
||||
new_entity = Entity(name=entity_name, entity_type="general", memory_count=1)
|
||||
graph.entities.append(new_entity)
|
||||
entity_dict[entity_name] = new_entity
|
||||
|
||||
save_memory_graph(graph)
|
||||
return memory
|
||||
|
||||
@staticmethod
|
||||
def get_all_memories(limit: int = 20) -> MemoryGraph:
|
||||
"""Get all memories and entities.
|
||||
|
||||
Returns:
|
||||
MemoryGraph: All stored memories and entities, sorted by timestamp (newest first).
|
||||
"""
|
||||
graph = read_memory_graph()
|
||||
# Sort memories by timestamp (newest first)
|
||||
graph.memories.sort(key=lambda m: m.timestamp, reverse=True)
|
||||
if limit and len(graph.memories) > limit:
|
||||
graph.memories = graph.memories[:limit]
|
||||
return graph
|
||||
|
||||
@staticmethod
|
||||
def search_memories(req: SearchMemoryRequest) -> MemoryGraph:
|
||||
"""Search memories by content or entity names.
|
||||
|
||||
Returns:
|
||||
MemoryGraph: Filtered memories matching the search query.
|
||||
"""
|
||||
graph = read_memory_graph()
|
||||
query = req.query.lower()
|
||||
|
||||
matching_memories = []
|
||||
for memory in graph.memories:
|
||||
# Search in content
|
||||
if query in memory.content.lower():
|
||||
matching_memories.append(memory)
|
||||
continue
|
||||
# Search in entity names
|
||||
if any(query in entity.lower() for entity in memory.entities):
|
||||
matching_memories.append(memory)
|
||||
|
||||
# Sort by timestamp (newest first) and apply limit
|
||||
matching_memories.sort(key=lambda m: m.timestamp, reverse=True)
|
||||
if len(matching_memories) > req.limit:
|
||||
matching_memories = matching_memories[: req.limit]
|
||||
|
||||
# Get entities referenced in matching memories
|
||||
referenced_entities = set()
|
||||
for memory in matching_memories:
|
||||
referenced_entities.update(memory.entities)
|
||||
|
||||
matching_entities = [e for e in graph.entities if e.name in referenced_entities]
|
||||
|
||||
return MemoryGraph(memories=matching_memories, entities=matching_entities)
|
||||
|
||||
@staticmethod
|
||||
def get_entity_memories(req: GetEntityRequest) -> MemoryGraph:
|
||||
"""Get memories for specific entities.
|
||||
|
||||
Returns:
|
||||
MemoryGraph: Memories that reference the specified entities.
|
||||
"""
|
||||
graph = read_memory_graph()
|
||||
|
||||
# Check if memory references any of the requested entities
|
||||
matching_memories = [
|
||||
memory
|
||||
for memory in graph.memories
|
||||
if any(entity in memory.entities for entity in req.entities)
|
||||
]
|
||||
|
||||
# Sort by timestamp (newest first) and apply limit
|
||||
matching_memories.sort(key=lambda m: m.timestamp, reverse=True)
|
||||
if len(matching_memories) > req.limit:
|
||||
matching_memories = matching_memories[: req.limit]
|
||||
|
||||
# Get the requested entities
|
||||
matching_entities = [e for e in graph.entities if e.name in req.entities]
|
||||
|
||||
return MemoryGraph(memories=matching_memories, entities=matching_entities)
|
||||
|
||||
@staticmethod
|
||||
def delete_memories(req: DeleteMemoryRequest) -> dict[str, str]:
|
||||
"""Delete specific memories by ID.
|
||||
|
||||
Returns:
|
||||
dict: Success message with count of deleted memories.
|
||||
"""
|
||||
graph = read_memory_graph()
|
||||
original_count = len(graph.memories)
|
||||
|
||||
# Remove memories and track which entities were affected
|
||||
affected_entities = set()
|
||||
graph.memories = [
|
||||
m
|
||||
for m in graph.memories
|
||||
if m.id not in req.memory_ids or affected_entities.update(m.entities)
|
||||
]
|
||||
|
||||
deleted_count = original_count - len(graph.memories)
|
||||
|
||||
# Recalculate entity memory counts
|
||||
entity_counts = {}
|
||||
for memory in graph.memories:
|
||||
for entity_name in memory.entities:
|
||||
entity_counts[entity_name] = entity_counts.get(entity_name, 0) + 1
|
||||
|
||||
# Update entity counts and remove entities with zero memories
|
||||
graph.entities = [
|
||||
Entity(
|
||||
name=e.name, entity_type=e.entity_type, memory_count=entity_counts.get(e.name, 0)
|
||||
)
|
||||
for e in graph.entities
|
||||
if entity_counts.get(e.name, 0) > 0
|
||||
]
|
||||
|
||||
save_memory_graph(graph)
|
||||
return {"message": f"Deleted {deleted_count} memories"}
|
||||
|
||||
@staticmethod
|
||||
def get_summary() -> MemorySummary:
|
||||
"""Get summary statistics about stored memories.
|
||||
|
||||
Returns:
|
||||
MemorySummary: Statistics about memories and entities.
|
||||
"""
|
||||
graph = read_memory_graph()
|
||||
|
||||
if not graph.memories:
|
||||
return MemorySummary(
|
||||
total_memories=0,
|
||||
total_entities=0,
|
||||
oldest_memory=None,
|
||||
latest_memory=None,
|
||||
memory_timespan_days=None,
|
||||
top_entities=[],
|
||||
)
|
||||
|
||||
# Sort memories by timestamp
|
||||
sorted_memories = sorted(graph.memories, key=lambda m: m.timestamp)
|
||||
oldest = sorted_memories[0].timestamp
|
||||
latest = sorted_memories[-1].timestamp
|
||||
|
||||
# Calculate timespan
|
||||
try:
|
||||
oldest_dt = datetime.fromisoformat(oldest)
|
||||
latest_dt = datetime.fromisoformat(latest)
|
||||
timespan_days = (latest_dt - oldest_dt).days
|
||||
except ValueError:
|
||||
timespan_days = None
|
||||
|
||||
# Get top entities by memory count
|
||||
top_entities = sorted(graph.entities, key=lambda e: e.memory_count, reverse=True)[:10]
|
||||
|
||||
return MemorySummary(
|
||||
total_memories=len(graph.memories),
|
||||
total_entities=len(graph.entities),
|
||||
oldest_memory=oldest,
|
||||
latest_memory=latest,
|
||||
memory_timespan_days=timespan_days,
|
||||
top_entities=top_entities,
|
||||
)
|
||||
|
||||
def get_router(self) -> APIRouter:
|
||||
"""Return the FastAPI router for memory tool endpoints."""
|
||||
router = APIRouter()
|
||||
|
||||
router.add_api_route(
|
||||
"/create",
|
||||
MemoryTool.create_memory,
|
||||
methods=["POST"],
|
||||
response_model=Memory,
|
||||
summary=(
|
||||
"Create a new memory. Before creating, check existing memories to avoid duplicates "
|
||||
"and to know what best to add. When you have information to store on multiple "
|
||||
"entities, favour creating multiple, detailed memories instead of one large, "
|
||||
"monolithic memory."
|
||||
),
|
||||
)
|
||||
|
||||
router.add_api_route(
|
||||
"/all",
|
||||
MemoryTool.get_all_memories,
|
||||
methods=["GET"],
|
||||
response_model=MemoryGraph,
|
||||
summary=("Retrieve all stored memories and entities (newest first)."),
|
||||
)
|
||||
|
||||
router.add_api_route(
|
||||
"/search",
|
||||
MemoryTool.search_memories,
|
||||
methods=["POST"],
|
||||
response_model=MemoryGraph,
|
||||
summary=(
|
||||
"Search for memories by keywords or entities. Use this to check for existing "
|
||||
"information before creating a new memory."
|
||||
),
|
||||
)
|
||||
|
||||
router.add_api_route(
|
||||
"/entity",
|
||||
MemoryTool.get_entity_memories,
|
||||
methods=["POST"],
|
||||
response_model=MemoryGraph,
|
||||
summary=(
|
||||
"Retrieve all memories for one or more entities. A useful first step to see what "
|
||||
"information is already stored for an entity before adding more."
|
||||
),
|
||||
)
|
||||
|
||||
router.add_api_route(
|
||||
"/delete",
|
||||
MemoryTool.delete_memories,
|
||||
methods=["POST"],
|
||||
summary=(
|
||||
"Remove specific memories by their IDs. Entity counts are automatically updated."
|
||||
),
|
||||
)
|
||||
|
||||
router.add_api_route(
|
||||
"/stats",
|
||||
MemoryTool.get_summary,
|
||||
methods=["GET"],
|
||||
response_model=MemorySummary,
|
||||
summary=("Get summary statistics on total memories and lifespan/frequency."),
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
tool = MemoryTool()
|
88
openapi_mcp_server/tools/memory/storage.py
Normal file
88
openapi_mcp_server/tools/memory/storage.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
"""Storage implementation for memory tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from .models import Entity, Memory, MemoryGraph
|
||||
|
||||
MEMORY_FILE_PATH_ENV = os.getenv("MEMORY_FILE_PATH", "memory.json")
|
||||
MEMORY_FILE_PATH = Path(
|
||||
MEMORY_FILE_PATH_ENV
|
||||
if Path(MEMORY_FILE_PATH_ENV).is_absolute()
|
||||
else Path(__file__).parent / MEMORY_FILE_PATH_ENV
|
||||
)
|
||||
|
||||
|
||||
def read_memory_graph() -> MemoryGraph:
|
||||
"""Read the memory graph from file.
|
||||
|
||||
Returns:
|
||||
MemoryGraph: The memory graph loaded from storage.
|
||||
|
||||
Raises:
|
||||
HTTPException: If the memory file is not found or permission is denied.
|
||||
"""
|
||||
if not MEMORY_FILE_PATH.exists():
|
||||
return MemoryGraph(memories=[], entities=[])
|
||||
|
||||
try:
|
||||
with MEMORY_FILE_PATH.open(encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
memories = [Memory(**m) for m in data.get("memories", [])]
|
||||
entities = [Entity(**e) for e in data.get("entities", [])]
|
||||
|
||||
return MemoryGraph(memories=memories, entities=entities)
|
||||
except PermissionError as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Permission denied when reading memory file: {MEMORY_FILE_PATH}",
|
||||
) from e
|
||||
except json.JSONDecodeError:
|
||||
# Handle legacy format or corrupted file
|
||||
return MemoryGraph(memories=[], entities=[])
|
||||
|
||||
|
||||
def save_memory_graph(graph: MemoryGraph) -> None:
|
||||
"""Save the memory graph to file.
|
||||
|
||||
Raises:
|
||||
HTTPException: If the memory file is not found or permission is denied.
|
||||
"""
|
||||
data = {
|
||||
"memories": [m.model_dump() for m in graph.memories],
|
||||
"entities": [e.model_dump() for e in graph.entities],
|
||||
}
|
||||
|
||||
try:
|
||||
with MEMORY_FILE_PATH.open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
except PermissionError as e:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Permission denied when writing to memory file: {MEMORY_FILE_PATH}",
|
||||
) from e
|
||||
|
||||
|
||||
def generate_memory_id() -> str:
|
||||
"""Generate a unique ID for a memory.
|
||||
|
||||
Returns:
|
||||
str: Unique memory identifier with timestamp.
|
||||
"""
|
||||
return f"mem_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S_%f')}"
|
||||
|
||||
|
||||
def get_current_timestamp() -> str:
|
||||
"""Get current UTC timestamp in ISO format.
|
||||
|
||||
Returns:
|
||||
str: UTC timestamp in ISO format with Z suffix.
|
||||
"""
|
||||
return datetime.now(UTC).isoformat() + "Z"
|
3
openapi_mcp_server/tools/searxng/__init__.py
Normal file
3
openapi_mcp_server/tools/searxng/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Search functionality via SearXNG."""
|
||||
|
||||
from __future__ import annotations
|
62
openapi_mcp_server/tools/searxng/models.py
Normal file
62
openapi_mcp_server/tools/searxng/models.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
"""Data models for SearXNG search tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
"""Request model for search operations."""
|
||||
|
||||
query: str = Field(..., description="Search query string")
|
||||
categories: str | None = Field(
|
||||
None, description="Comma-separated categories (e.g. 'general,web')"
|
||||
)
|
||||
engines: str | None = Field(None, description="Comma-separated search engines")
|
||||
language: str | None = Field("en", description="Language code (e.g. 'en', 'de')")
|
||||
format: str | None = Field("json", description="Response format (json, csv, rss)")
|
||||
pageno: int | None = Field(1, description="Page number")
|
||||
|
||||
|
||||
class SearchResult(BaseModel):
|
||||
"""Individual search result model."""
|
||||
|
||||
title: str = Field(..., description="Result title")
|
||||
url: str = Field(..., description="Result URL")
|
||||
content: str | None = Field(None, description="Result content/snippet")
|
||||
engine: str = Field(..., description="Search engine that provided this result")
|
||||
category: str = Field(..., description="Category of the result")
|
||||
|
||||
|
||||
class SearchResponse(BaseModel):
|
||||
"""Response model for search operations."""
|
||||
|
||||
query: str = Field(..., description="Original search query")
|
||||
number_of_results: int = Field(..., description="Total number of results found")
|
||||
results: list[SearchResult] = Field(..., description="Search results")
|
||||
infoboxes: list[dict[str, Any]] | None = Field(None, description="Information boxes")
|
||||
suggestions: list[str] | None = Field(None, description="Search suggestions")
|
||||
engines: list[str] | None = Field(None, description="Engines used for search")
|
||||
|
||||
|
||||
class CategoriesResponse(BaseModel):
|
||||
"""Response model for available categories."""
|
||||
|
||||
categories: list[str] = Field(..., description="Available search categories")
|
||||
note: str | None = Field(None, description="Additional notes about the response")
|
||||
|
||||
|
||||
class EnginesResponse(BaseModel):
|
||||
"""Response model for available engines."""
|
||||
|
||||
engines: list[str] = Field(..., description="Available search engines")
|
||||
note: str | None = Field(None, description="Additional notes about the response")
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
"""Health response model for SearXNG service."""
|
||||
|
||||
status: str = Field(..., description="Service health status")
|
||||
searxng_url: str = Field(..., description="SearXNG instance URL")
|
188
openapi_mcp_server/tools/searxng/routes.py
Normal file
188
openapi_mcp_server/tools/searxng/routes.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
"""API routes for SearXNG search tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import requests
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from openapi_mcp_server.tools.base import BaseTool
|
||||
|
||||
from .models import (
|
||||
CategoriesResponse,
|
||||
EnginesResponse,
|
||||
SearchRequest,
|
||||
SearchResponse,
|
||||
SearchResult,
|
||||
)
|
||||
|
||||
SEARXNG_BASE_URL = os.getenv("SEARXNG_BASE_URL", "http://localhost:8080")
|
||||
|
||||
|
||||
class SearxngTool(BaseTool):
|
||||
"""SearXNG search proxy tool."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the searxng tool."""
|
||||
super().__init__(
|
||||
name="searxng",
|
||||
description="Proxy server for SearXNG search instances with OpenAPI compatibility",
|
||||
)
|
||||
|
||||
def get_router(self) -> APIRouter:
|
||||
"""Return the FastAPI router for searxng tool endpoints."""
|
||||
router = APIRouter()
|
||||
|
||||
@router.post(
|
||||
"/search",
|
||||
response_model=SearchResponse,
|
||||
summary="Search the web across multiple search engines",
|
||||
)
|
||||
def search(request: SearchRequest) -> SearchResponse:
|
||||
"""Search the web across multiple search engines.
|
||||
|
||||
Returns:
|
||||
SearchResponse: Search results from SearXNG.
|
||||
"""
|
||||
return SearxngTool._perform_search(
|
||||
request.query,
|
||||
request.categories,
|
||||
request.engines,
|
||||
request.language,
|
||||
request.format,
|
||||
request.pageno,
|
||||
)
|
||||
|
||||
@router.get(
|
||||
"/categories",
|
||||
response_model=CategoriesResponse,
|
||||
summary="Get available search categories (general, images, news, etc)",
|
||||
)
|
||||
def categories() -> CategoriesResponse:
|
||||
"""Get available search categories from SearXNG.
|
||||
|
||||
Returns:
|
||||
CategoriesResponse: Available search categories.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(f"{SEARXNG_BASE_URL}/config", timeout=10)
|
||||
response.raise_for_status()
|
||||
config = response.json()
|
||||
|
||||
categories = config.get("categories", [])
|
||||
return CategoriesResponse(categories=categories or [])
|
||||
|
||||
except Exception as e:
|
||||
return CategoriesResponse(
|
||||
categories=[
|
||||
"general",
|
||||
"images",
|
||||
"videos",
|
||||
"news",
|
||||
"map",
|
||||
"music",
|
||||
"it",
|
||||
"science",
|
||||
"files",
|
||||
"social media",
|
||||
],
|
||||
note=f"Using default categories (SearXNG config unavailable: {e!s})",
|
||||
)
|
||||
|
||||
@router.get(
|
||||
"/engines",
|
||||
response_model=EnginesResponse,
|
||||
summary="Get list of available search engines (Google, Bing, DuckDuckGo, etc)",
|
||||
)
|
||||
def engines() -> EnginesResponse:
|
||||
"""Get available search engines from SearXNG.
|
||||
|
||||
Returns:
|
||||
EnginesResponse: Available search engines.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(f"{SEARXNG_BASE_URL}/config", timeout=10)
|
||||
response.raise_for_status()
|
||||
config = response.json()
|
||||
|
||||
engines = config.get("engines", [])
|
||||
engine_names = [engine.get("name") for engine in engines if engine.get("name")]
|
||||
return EnginesResponse(engines=engine_names)
|
||||
|
||||
except Exception as e:
|
||||
return EnginesResponse(
|
||||
engines=[],
|
||||
note=f"Unable to fetch engines from SearXNG: {e!s}",
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
@staticmethod
|
||||
def _perform_search(
|
||||
query: str,
|
||||
categories: str | None = None,
|
||||
engines: str | None = None,
|
||||
language: str = "en",
|
||||
response_format: str = "json",
|
||||
pageno: int = 1,
|
||||
) -> SearchResponse:
|
||||
"""Internal function to perform the actual search.
|
||||
|
||||
Returns:
|
||||
SearchResponse: Search results from SearXNG.
|
||||
|
||||
Raises:
|
||||
HTTPException: If search fails or service is unavailable.
|
||||
"""
|
||||
params = {"q": query, "format": response_format, "lang": language, "pageno": pageno}
|
||||
|
||||
if categories:
|
||||
params["categories"] = categories
|
||||
|
||||
if engines:
|
||||
params["engines"] = engines
|
||||
|
||||
try:
|
||||
response = requests.get(f"{SEARXNG_BASE_URL}/search", params=params, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
if response_format.lower() != "json":
|
||||
raise HTTPException(status_code=400, detail="Only JSON format is supported")
|
||||
|
||||
data = response.json()
|
||||
|
||||
results = []
|
||||
for result in data.get("results", []):
|
||||
search_result = SearchResult(
|
||||
title=result.get("title", ""),
|
||||
url=result.get("url", ""),
|
||||
content=result.get("content", result.get("snippet", "")),
|
||||
engine=result.get("engine", "unknown"),
|
||||
category=result.get("category", "general"),
|
||||
)
|
||||
results.append(search_result)
|
||||
|
||||
return SearchResponse(
|
||||
query=query,
|
||||
number_of_results=data.get("number_of_results", len(results)),
|
||||
results=results,
|
||||
infoboxes=data.get("infoboxes"),
|
||||
suggestions=data.get("suggestions"),
|
||||
engines=data.get("engines"),
|
||||
)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"SearXNG service unavailable: {e!s}"
|
||||
) from e
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(
|
||||
status_code=502, detail=f"Invalid JSON response from SearXNG: {e!s}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Search error: {e!s}") from e
|
||||
|
||||
|
||||
tool = SearxngTool()
|
3
openapi_mcp_server/tools/time/__init__.py
Normal file
3
openapi_mcp_server/tools/time/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Time-related operations tool."""
|
||||
|
||||
from __future__ import annotations
|
76
openapi_mcp_server/tools/time/models.py
Normal file
76
openapi_mcp_server/tools/time/models.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
"""Data models for time operations tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ConvertTimeInput(BaseModel):
|
||||
"""Input model for converting time between timezones."""
|
||||
|
||||
timestamp: str = Field(
|
||||
..., description="ISO 8601 formatted time string (e.g., 2024-01-01T12:00:00Z)"
|
||||
)
|
||||
from_tz: str = Field(
|
||||
..., description="Original IANA time zone of input (e.g. UTC or Europe/Berlin)"
|
||||
)
|
||||
to_tz: str = Field(..., description="Target IANA time zone to convert to")
|
||||
|
||||
|
||||
class ElapsedTimeInput(BaseModel):
|
||||
"""Input model for calculating elapsed time."""
|
||||
|
||||
start: str = Field(..., description="Start timestamp in ISO 8601 format")
|
||||
end: str = Field(..., description="End timestamp in ISO 8601 format")
|
||||
units: Literal["seconds", "minutes", "hours", "days"] = Field(
|
||||
"seconds", description="Unit for elapsed time"
|
||||
)
|
||||
|
||||
|
||||
class ParseTimestampInput(BaseModel):
|
||||
"""Input model for parsing timestamp strings."""
|
||||
|
||||
timestamp: str = Field(
|
||||
..., description="Flexible input timestamp string (e.g., 2024-06-01 12:00 PM)"
|
||||
)
|
||||
timezone: str = Field("UTC", description="Assumed timezone if none is specified in input")
|
||||
|
||||
|
||||
class UnixToIsoInput(BaseModel):
|
||||
"""Input model for converting Unix timestamp to ISO format."""
|
||||
|
||||
timestamp: float = Field(..., description="Unix epoch timestamp (seconds since 1970-01-01)")
|
||||
timezone: str = Field("UTC", description="Target timezone for output (defaults to UTC)")
|
||||
|
||||
|
||||
class TimeResponse(BaseModel):
|
||||
"""Response model for UTC time."""
|
||||
|
||||
utc: str = Field(..., description="UTC time in ISO format")
|
||||
|
||||
|
||||
class ConvertedTimeResponse(BaseModel):
|
||||
"""Response model for converted time."""
|
||||
|
||||
converted_time: str = Field(..., description="Converted time in ISO format")
|
||||
|
||||
|
||||
class ElapsedTimeResponse(BaseModel):
|
||||
"""Response model for elapsed time calculation."""
|
||||
|
||||
elapsed: float = Field(..., description="Elapsed time in specified units")
|
||||
unit: str = Field(..., description="Unit of elapsed time")
|
||||
|
||||
|
||||
class ParsedTimestampResponse(BaseModel):
|
||||
"""Response model for parsed timestamp."""
|
||||
|
||||
utc: str = Field(..., description="Parsed timestamp in UTC ISO format")
|
||||
|
||||
|
||||
class UnixToIsoResponse(BaseModel):
|
||||
"""Response model for Unix to ISO conversion."""
|
||||
|
||||
iso_time: str = Field(..., description="ISO formatted timestamp")
|
210
openapi_mcp_server/tools/time/routes.py
Normal file
210
openapi_mcp_server/tools/time/routes.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
"""API routes for time operations tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytz
|
||||
from dateutil import parser as dateutil_parser
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from openapi_mcp_server.tools.base import BaseTool
|
||||
|
||||
from .models import (
|
||||
ConvertedTimeResponse,
|
||||
ConvertTimeInput,
|
||||
ElapsedTimeInput,
|
||||
ElapsedTimeResponse,
|
||||
ParsedTimestampResponse,
|
||||
ParseTimestampInput,
|
||||
TimeResponse,
|
||||
UnixToIsoInput,
|
||||
UnixToIsoResponse,
|
||||
)
|
||||
|
||||
|
||||
class TimeTool(BaseTool):
|
||||
"""Secure time utilities tool."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the time tool."""
|
||||
super().__init__(
|
||||
name="time",
|
||||
description="Provides secure UTC/local time retrieval and formatting",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_current(timezone: str = "UTC") -> TimeResponse:
|
||||
"""Get current time in specified timezone (defaults to UTC).
|
||||
|
||||
Args:
|
||||
timezone: IANA timezone name (e.g. 'UTC', 'America/New_York')
|
||||
|
||||
Returns:
|
||||
TimeResponse: Current time in ISO format for specified timezone.
|
||||
|
||||
Raises:
|
||||
HTTPException: If timezone is invalid.
|
||||
"""
|
||||
try:
|
||||
tz = pytz.timezone(timezone)
|
||||
now = datetime.now(tz)
|
||||
return TimeResponse(utc=now.isoformat())
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid timezone: {e}") from e
|
||||
|
||||
@staticmethod
|
||||
def unix_to_iso(data: UnixToIsoInput) -> UnixToIsoResponse:
|
||||
"""Convert Unix epoch timestamp to ISO format.
|
||||
|
||||
Args:
|
||||
data: Unix timestamp and optional timezone
|
||||
|
||||
Returns:
|
||||
UnixToIsoResponse: ISO formatted timestamp.
|
||||
|
||||
Raises:
|
||||
HTTPException: If timestamp or timezone is invalid.
|
||||
"""
|
||||
try:
|
||||
dt = datetime.fromtimestamp(data.timestamp, tz=UTC)
|
||||
if data.timezone and data.timezone != "UTC":
|
||||
target_tz = pytz.timezone(data.timezone)
|
||||
dt = dt.astimezone(target_tz)
|
||||
return UnixToIsoResponse(iso_time=dt.isoformat())
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Invalid timestamp or timezone: {e}"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def convert_time(data: ConvertTimeInput) -> ConvertedTimeResponse:
|
||||
"""Convert a timestamp from one timezone to another.
|
||||
|
||||
Returns:
|
||||
ConvertedTimeResponse: Timestamp converted to target timezone.
|
||||
|
||||
Raises:
|
||||
HTTPException: If timezone or timestamp is invalid.
|
||||
"""
|
||||
try:
|
||||
from_zone = pytz.timezone(data.from_tz)
|
||||
to_zone = pytz.timezone(data.to_tz)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid timezone: {e}") from e
|
||||
|
||||
try:
|
||||
dt = dateutil_parser.parse(data.timestamp)
|
||||
dt = from_zone.localize(dt) if dt.tzinfo is None else dt.astimezone(from_zone)
|
||||
converted = dt.astimezone(to_zone)
|
||||
return ConvertedTimeResponse(converted_time=converted.isoformat())
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid timestamp: {e}") from e
|
||||
|
||||
@staticmethod
|
||||
def elapsed_time(data: ElapsedTimeInput) -> ElapsedTimeResponse:
|
||||
"""Calculate the difference between two timestamps in chosen units.
|
||||
|
||||
Returns:
|
||||
ElapsedTimeResponse: Time difference in specified units.
|
||||
|
||||
Raises:
|
||||
HTTPException: If timestamps are invalid.
|
||||
"""
|
||||
try:
|
||||
start_dt = dateutil_parser.parse(data.start)
|
||||
end_dt = dateutil_parser.parse(data.end)
|
||||
delta = end_dt - start_dt
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid timestamps: {e}") from e
|
||||
|
||||
seconds = delta.total_seconds()
|
||||
result = {
|
||||
"seconds": seconds,
|
||||
"minutes": seconds / 60,
|
||||
"hours": seconds / 3600,
|
||||
"days": seconds / 86400,
|
||||
}
|
||||
|
||||
return ElapsedTimeResponse(elapsed=result[data.units], unit=data.units)
|
||||
|
||||
@staticmethod
|
||||
def parse_timestamp(data: ParseTimestampInput) -> ParsedTimestampResponse:
|
||||
"""Parse human-friendly input timestamp and return standardized UTC ISO time.
|
||||
|
||||
Returns:
|
||||
ParsedTimestampResponse: Standardized UTC timestamp.
|
||||
|
||||
Raises:
|
||||
HTTPException: If timestamp cannot be parsed.
|
||||
"""
|
||||
try:
|
||||
tz = pytz.timezone(data.timezone)
|
||||
dt = dateutil_parser.parse(data.timestamp)
|
||||
if dt.tzinfo is None:
|
||||
dt = tz.localize(dt)
|
||||
dt_utc = dt.astimezone(pytz.utc)
|
||||
return ParsedTimestampResponse(utc=dt_utc.isoformat())
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=f"Could not parse: {e}") from e
|
||||
|
||||
@staticmethod
|
||||
def list_time_zones() -> list[str]:
|
||||
"""Return a list of all valid IANA time zones."""
|
||||
return list(pytz.all_timezones)
|
||||
|
||||
def get_router(self) -> APIRouter:
|
||||
"""Return the FastAPI router for time tool endpoints."""
|
||||
router = APIRouter()
|
||||
|
||||
@router.get(
|
||||
"/get_time",
|
||||
response_model=TimeResponse,
|
||||
summary="Get current time in specified IANA timezone (defaults to UTC)",
|
||||
)
|
||||
def get_time(timezone: str = "UTC") -> TimeResponse:
|
||||
return TimeTool.get_current(timezone)
|
||||
|
||||
@router.post(
|
||||
"/unix_to_iso",
|
||||
response_model=UnixToIsoResponse,
|
||||
summary="Convert Unix epoch timestamp to ISO format",
|
||||
)
|
||||
def unix_to_iso(data: UnixToIsoInput) -> UnixToIsoResponse:
|
||||
return TimeTool.unix_to_iso(data)
|
||||
|
||||
@router.post(
|
||||
"/convert_time",
|
||||
response_model=ConvertedTimeResponse,
|
||||
summary="Convert timestamp from one timezone to another",
|
||||
)
|
||||
def convert_time(data: ConvertTimeInput) -> ConvertedTimeResponse:
|
||||
return TimeTool.convert_time(data)
|
||||
|
||||
@router.post(
|
||||
"/elapsed_time",
|
||||
response_model=ElapsedTimeResponse,
|
||||
summary="Calculate time difference between two timestamps",
|
||||
)
|
||||
def elapsed_time(data: ElapsedTimeInput) -> ElapsedTimeResponse:
|
||||
return TimeTool.elapsed_time(data)
|
||||
|
||||
@router.post(
|
||||
"/parse_timestamp",
|
||||
response_model=ParsedTimestampResponse,
|
||||
summary=(
|
||||
"Parse flexible human-readable timestamps (e.g. 'June 1st 2024 3:30 PM', "
|
||||
"'tomorrow at noon', '2024-06-01 15:30') into standardized UTC ISO format"
|
||||
),
|
||||
)
|
||||
def parse_timestamp(data: ParseTimestampInput) -> ParsedTimestampResponse:
|
||||
return TimeTool.parse_timestamp(data)
|
||||
|
||||
@router.get("/list_time_zones", summary="Get list of all valid IANA timezone names")
|
||||
def list_time_zones() -> list[str]:
|
||||
return TimeTool.list_time_zones()
|
||||
|
||||
return router
|
||||
|
||||
|
||||
tool = TimeTool()
|
3
openapi_mcp_server/tools/weather/__init__.py
Normal file
3
openapi_mcp_server/tools/weather/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Weather information tool."""
|
||||
|
||||
from __future__ import annotations
|
52
openapi_mcp_server/tools/weather/models.py
Normal file
52
openapi_mcp_server/tools/weather/models.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
"""Data models for weather information tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CurrentWeather(BaseModel):
|
||||
"""Current weather conditions model."""
|
||||
|
||||
time: str = Field(..., description="ISO 8601 format timestamp")
|
||||
temperature_2m: float = Field(
|
||||
...,
|
||||
alias="temperature_2m",
|
||||
description="Air temperature at 2 meters above ground",
|
||||
)
|
||||
wind_speed_10m: float = Field(
|
||||
..., alias="wind_speed_10m", description="Wind speed at 10 meters above ground"
|
||||
)
|
||||
|
||||
|
||||
class HourlyUnits(BaseModel):
|
||||
"""Units for hourly weather data."""
|
||||
|
||||
time: str
|
||||
temperature_2m: str
|
||||
relative_humidity_2m: str
|
||||
wind_speed_10m: str
|
||||
|
||||
|
||||
class HourlyData(BaseModel):
|
||||
"""Hourly weather data model."""
|
||||
|
||||
time: list[str]
|
||||
temperature_2m: list[float]
|
||||
relative_humidity_2m: list[int]
|
||||
wind_speed_10m: list[float]
|
||||
|
||||
|
||||
class WeatherForecastOutput(BaseModel):
|
||||
"""Complete weather forecast output model."""
|
||||
|
||||
latitude: float
|
||||
longitude: float
|
||||
generationtime_ms: float
|
||||
utc_offset_seconds: int
|
||||
timezone: str
|
||||
timezone_abbreviation: str
|
||||
elevation: float
|
||||
current: CurrentWeather = Field(..., description="Current weather conditions")
|
||||
hourly_units: HourlyUnits
|
||||
hourly: HourlyData
|
104
openapi_mcp_server/tools/weather/routes.py
Normal file
104
openapi_mcp_server/tools/weather/routes.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
"""API routes for weather information tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import requests
|
||||
import reverse_geocoder as rg
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from openapi_mcp_server.tools.base import BaseTool
|
||||
|
||||
from .models import WeatherForecastOutput
|
||||
|
||||
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
|
||||
FAHRENHEIT_COUNTRIES = {"US", "LR", "MM"}
|
||||
|
||||
|
||||
class WeatherTool(BaseTool):
|
||||
"""Weather forecast tool using Open-Meteo API."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the weather tool."""
|
||||
super().__init__(
|
||||
name="weather",
|
||||
description="Provides weather retrieval by latitude and longitude using Open-Meteo",
|
||||
)
|
||||
|
||||
def get_router(self) -> APIRouter:
|
||||
"""Return the FastAPI router for weather tool endpoints."""
|
||||
router = APIRouter()
|
||||
|
||||
@router.get(
|
||||
"/forecast",
|
||||
response_model=WeatherForecastOutput,
|
||||
summary="Get current weather conditions and hourly forecast by coordinates",
|
||||
)
|
||||
def forecast(
|
||||
latitude: Annotated[
|
||||
float | None, Query(description="Latitude for the location (e.g. 52.52)")
|
||||
] = None,
|
||||
longitude: Annotated[
|
||||
float | None, Query(description="Longitude for the location (e.g. 13.41)")
|
||||
] = None,
|
||||
) -> WeatherForecastOutput:
|
||||
"""Retrieves current weather conditions and hourly forecast for the given coordinates.
|
||||
|
||||
Temperature unit (Celsius/Fahrenheit) is determined automatically based on location.
|
||||
|
||||
Returns:
|
||||
WeatherForecastOutput: Current weather conditions and forecast data.
|
||||
|
||||
Raises:
|
||||
HTTPException: If weather API request fails or returns invalid data.
|
||||
"""
|
||||
if latitude is None or longitude is None:
|
||||
raise HTTPException(status_code=422, detail="Latitude and longitude are required.")
|
||||
# Determine temperature unit based on location
|
||||
try:
|
||||
geo_results = rg.search((latitude, longitude), mode=1)
|
||||
if geo_results:
|
||||
country_code = geo_results[0]["cc"]
|
||||
temperature_unit = (
|
||||
"fahrenheit" if country_code in FAHRENHEIT_COUNTRIES else "celsius"
|
||||
)
|
||||
else:
|
||||
temperature_unit = "celsius"
|
||||
except Exception:
|
||||
temperature_unit = "celsius"
|
||||
|
||||
params = {
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"current": "temperature_2m,wind_speed_10m",
|
||||
"hourly": "temperature_2m,relative_humidity_2m,wind_speed_10m",
|
||||
"timezone": "auto",
|
||||
"temperature_unit": temperature_unit,
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(OPEN_METEO_URL, params=params, timeout=30)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if "current" not in data or "hourly" not in data:
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Unexpected response format from Open-Meteo API"
|
||||
)
|
||||
|
||||
return WeatherForecastOutput(**data)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"Error connecting to Open-Meteo API: {e}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"An internal error occurred: {e}"
|
||||
) from e
|
||||
|
||||
return router
|
||||
|
||||
|
||||
tool = WeatherTool()
|
3
openapi_mcp_server/tools/web/__init__.py
Normal file
3
openapi_mcp_server/tools/web/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""Web content parsing tool."""
|
||||
|
||||
from __future__ import annotations
|
36
openapi_mcp_server/tools/web/models.py
Normal file
36
openapi_mcp_server/tools/web/models.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""Data models for web content parsing tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
|
||||
|
||||
class WebRequest(BaseModel):
|
||||
"""Request model for web content parsing."""
|
||||
|
||||
url: HttpUrl = Field(..., description="URL to parse and extract content from")
|
||||
favor_recall: bool | None = Field(True, description="Favor recall over precision in extraction")
|
||||
with_metadata: bool | None = Field(True, description="Include metadata in extraction")
|
||||
include_formatting: bool | None = Field(True, description="Keep formatting in output")
|
||||
include_images: bool | None = Field(True, description="Include images in output")
|
||||
include_links: bool | None = Field(True, description="Include links in output")
|
||||
include_tables: bool | None = Field(True, description="Include tables in output")
|
||||
|
||||
|
||||
class WebResponse(BaseModel):
|
||||
"""Response model for parsed web content."""
|
||||
|
||||
url: str = Field(..., description="Original URL that was parsed")
|
||||
content: str = Field(
|
||||
...,
|
||||
description="Extracted content in Markdown format with metadata frontmatter",
|
||||
)
|
||||
|
||||
|
||||
class WebRawResponse(BaseModel):
|
||||
"""Response model for raw web content."""
|
||||
|
||||
url: str = Field(..., description="Original URL that was fetched")
|
||||
status_code: int = Field(..., description="HTTP status code")
|
||||
headers: dict[str, str] = Field(..., description="Response headers")
|
||||
content: str = Field(..., description="Raw HTML content")
|
153
openapi_mcp_server/tools/web/routes.py
Normal file
153
openapi_mcp_server/tools/web/routes.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
"""API routes for web content parsing tool."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import requests
|
||||
import trafilatura
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import HttpUrl # noqa: TC002
|
||||
|
||||
from openapi_mcp_server.tools.base import BaseTool
|
||||
|
||||
from .models import WebRawResponse, WebRequest, WebResponse
|
||||
|
||||
|
||||
class WebTool(BaseTool):
|
||||
"""Web content parsing tool."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the web tool."""
|
||||
super().__init__(
|
||||
name="web",
|
||||
description="Extract and parse web page content into Markdown using trafilatura",
|
||||
)
|
||||
|
||||
def get_router(self) -> APIRouter:
|
||||
"""Return the FastAPI router for web tool endpoints."""
|
||||
router = APIRouter()
|
||||
|
||||
@router.post(
|
||||
"/web_read",
|
||||
response_model=WebResponse,
|
||||
summary="Extract and parse webpage content into clean markdown",
|
||||
)
|
||||
def read(request: WebRequest) -> WebResponse:
|
||||
"""Extract and parse webpage content into clean markdown.
|
||||
|
||||
Returns:
|
||||
WebResponse: Parsed web content.
|
||||
"""
|
||||
return WebTool._parse_web_page(
|
||||
str(request.url),
|
||||
request.with_metadata,
|
||||
request.include_formatting,
|
||||
request.include_images,
|
||||
request.include_links,
|
||||
request.include_tables,
|
||||
)
|
||||
|
||||
@router.get(
|
||||
"/web_raw",
|
||||
response_model=WebRawResponse,
|
||||
summary="Fetch raw HTML content and headers from any URL",
|
||||
)
|
||||
def raw(
|
||||
url: Annotated[
|
||||
HttpUrl | None, Query(description="URL to fetch raw content and headers from")
|
||||
] = None,
|
||||
) -> WebRawResponse:
|
||||
"""Fetch raw HTML content and headers from URL.
|
||||
|
||||
Returns:
|
||||
WebRawResponse: Raw HTML content and headers.
|
||||
|
||||
Raises:
|
||||
HTTPException: If request fails or URL is invalid.
|
||||
"""
|
||||
if not url:
|
||||
raise HTTPException(status_code=422, detail="URL query parameter is required.")
|
||||
|
||||
try:
|
||||
response = requests.get(str(url), timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
headers = dict(response.headers)
|
||||
|
||||
return WebRawResponse(
|
||||
url=str(url),
|
||||
status_code=response.status_code,
|
||||
headers=headers,
|
||||
content=response.text,
|
||||
)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Unable to fetch content from URL: {e!s}",
|
||||
) from e
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=500, detail=f"Error fetching raw content: {e!s}"
|
||||
) from e
|
||||
|
||||
return router
|
||||
|
||||
@staticmethod
|
||||
def _parse_web_page(
|
||||
url: str,
|
||||
with_metadata: bool = True,
|
||||
include_formatting: bool = True,
|
||||
include_images: bool = True,
|
||||
include_links: bool = True,
|
||||
include_tables: bool = True,
|
||||
) -> WebResponse:
|
||||
"""Internal function to perform the actual web page parsing.
|
||||
|
||||
Returns:
|
||||
WebResponse: Parsed web content.
|
||||
|
||||
Raises:
|
||||
HTTPException: If parsing fails or URL is invalid.
|
||||
"""
|
||||
try:
|
||||
downloaded = trafilatura.fetch_url(url)
|
||||
if not downloaded:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Unable to fetch content from URL: {url}"
|
||||
)
|
||||
|
||||
config = trafilatura.settings.use_config()
|
||||
config.set("DEFAULT", "EXTRACTION_TIMEOUT", "30")
|
||||
|
||||
extracted_content = trafilatura.extract(
|
||||
downloaded,
|
||||
output_format="markdown",
|
||||
favor_recall=True,
|
||||
with_metadata=with_metadata,
|
||||
include_formatting=include_formatting,
|
||||
include_images=include_images,
|
||||
include_links=include_links,
|
||||
include_tables=include_tables,
|
||||
config=config,
|
||||
)
|
||||
|
||||
if not extracted_content:
|
||||
raise HTTPException(
|
||||
status_code=422, detail=f"Unable to extract content from URL: {url}"
|
||||
)
|
||||
|
||||
return WebResponse(
|
||||
url=url,
|
||||
content=extracted_content,
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error parsing web page: {e!s}") from e
|
||||
|
||||
|
||||
# Create the tool instance that will be discovered by the registry
|
||||
tool = WebTool()
|
94
pyproject.toml
Normal file
94
pyproject.toml
Normal file
|
@ -0,0 +1,94 @@
|
|||
[project]
|
||||
name = "openapi-mcp-server"
|
||||
version = "0.1.0"
|
||||
description = "An OpenAPI-compatible server for the Multi-agent Conversation Platform (MCP)."
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
authors = [{ name = "Tom Foster", email = "tom@tcpip.uk" }]
|
||||
maintainers = [{ name = "Tom Foster", email = "tom@tcpip.uk" }]
|
||||
requires-python = ">=3.13"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Topic :: Communications :: Chat",
|
||||
"Framework :: AsyncIO",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
dependencies = [
|
||||
"fastapi>=0",
|
||||
"pydantic>=2",
|
||||
"pydantic-settings>=2",
|
||||
"python-dateutil>=2",
|
||||
"python-multipart>=0",
|
||||
"pytz>=2025",
|
||||
"requests>=2",
|
||||
"reverse-geocoder>=1",
|
||||
"trafilatura>=2",
|
||||
"uvicorn[standard]>=0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://git.tomfos.tr/tom/openapi-mcp-server"
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ruff>=0"]
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project.scripts]
|
||||
openapi-mcp-server = "openapi_mcp_server.__main__:launch"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = { find = {} }
|
||||
|
||||
[tool.ruff]
|
||||
cache-dir = "/tmp/.ruff_cache"
|
||||
fix = true
|
||||
line-length = 100
|
||||
preview = true
|
||||
show-fixes = false
|
||||
target-version = "py313"
|
||||
unsafe-fixes = true
|
||||
|
||||
[tool.ruff.format]
|
||||
line-ending = "auto"
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
[tool.ruff.lint]
|
||||
fixable = ["ALL"]
|
||||
ignore = [
|
||||
"ANN401", # use of Any type
|
||||
"BLE001", # blind Exception usage
|
||||
"COM812", # missing trailing comma
|
||||
"CPY", # flake8-copyright
|
||||
"FBT", # boolean arguments
|
||||
"PLR0912", # too many branches
|
||||
"PLR0913", # too many arguments
|
||||
"PLR0915", # too many statements
|
||||
"PLR0917", # too many positional arguments
|
||||
"PLR6301", # method could be static
|
||||
"RUF029", # async methods that don't await
|
||||
"S104", # binding to all interfaces
|
||||
"S110", # passed exceptions
|
||||
"TRY301", # raise inside try block
|
||||
]
|
||||
select = ["ALL"]
|
||||
unfixable = [
|
||||
"F841", # local variable assigned but never used
|
||||
"RUF100", # unused noqa comments
|
||||
"T201", # don't strip print statement
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
combine-as-imports = true
|
||||
required-imports = ["from __future__ import annotations"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
736
uv.lock
generated
Normal file
736
uv.lock
generated
Normal file
|
@ -0,0 +1,736 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.6.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "courlan"
|
||||
version = "1.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "babel" },
|
||||
{ name = "tld" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/54/6d6ceeff4bed42e7a10d6064d35ee43a810e7b3e8beb4abeae8cff4713ae/courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190", size = 206382, upload-time = "2024-10-29T16:40:20.994Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848, upload-time = "2024-10-29T16:40:18.325Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dateparser"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "regex" },
|
||||
{ name = "tzlocal" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/3f/d3207a05f5b6a78c66d86631e60bfba5af163738a599a5b9aa2c2737a09e/dateparser-1.2.1.tar.gz", hash = "sha256:7e4919aeb48481dbfc01ac9683c8e20bfe95bb715a38c1e9f6af889f4f30ccc3", size = 309924, upload-time = "2025-02-05T12:34:55.593Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/0a/981c438c4cd84147c781e4e96c1d72df03775deb1bc76c5a6ee8afa89c62/dateparser-1.2.1-py3-none-any.whl", hash = "sha256:bdcac262a467e6260030040748ad7c10d6bacd4f3b9cdb4cfd2251939174508c", size = 295658, upload-time = "2025-02-05T12:34:53.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "htmldate"
|
||||
version = "1.9.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "dateparser" },
|
||||
{ name = "lxml" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a5/26/aaae4cab984f0b7dd0f5f1b823fa2ed2fd4a2bb50acd5bd2f0d217562678/htmldate-1.9.3.tar.gz", hash = "sha256:ac0caf4628c3ded4042011e2d60dc68dfb314c77b106587dd307a80d77e708e9", size = 44913, upload-time = "2024-12-30T12:52:35.206Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/49/8872130016209c20436ce0c1067de1cf630755d0443d068a5bc17fa95015/htmldate-1.9.3-py3-none-any.whl", hash = "sha256:3fadc422cf3c10a5cdb5e1b914daf37ec7270400a80a1b37e2673ff84faaaff8", size = 31565, upload-time = "2024-12-30T12:52:32.145Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.6.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "justext"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml", extra = ["html-clean"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/f3/45890c1b314f0d04e19c1c83d534e611513150939a7cf039664d9ab1e649/justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05", size = 828521, upload-time = "2025-02-25T20:21:49.934Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940, upload-time = "2025-02-25T20:21:44.179Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "5.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
html-clean = [
|
||||
{ name = "lxml-html-clean" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lxml-html-clean"
|
||||
version = "0.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/b6/466e71db127950fb8d172026a8f0a9f0dc6f64c8e78e2ca79f252e5790b8/lxml_html_clean-0.4.2.tar.gz", hash = "sha256:91291e7b5db95430abf461bc53440964d58e06cc468950f9e47db64976cebcb3", size = 21622, upload-time = "2025-04-09T11:33:59.432Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/0b/942cb7278d6caad79343ad2ddd636ed204a47909b969d19114a3097f5aa3/lxml_html_clean-0.4.2-py3-none-any.whl", hash = "sha256:74ccfba277adcfea87a1e9294f47dd86b05d65b4da7c5b07966e3d5f3be8a505", size = 14184, upload-time = "2025-04-09T11:33:57.988Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/db/8e12381333aea300890829a0a36bfa738cac95475d88982d538725143fd9/numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6", size = 20382813, upload-time = "2025-06-07T14:54:32.608Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/fc/1d67f751fd4dbafc5780244fe699bc4084268bad44b7c5deb0492473127b/numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a", size = 20889633, upload-time = "2025-06-07T14:44:06.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/95/73ffdb69e5c3f19ec4530f8924c4386e7ba097efc94b9c0aff607178ad94/numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959", size = 14151683, upload-time = "2025-06-07T14:44:28.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/d5/06d4bb31bb65a1d9c419eb5676173a2f90fd8da3c59f816cc54c640ce265/numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe", size = 5102683, upload-time = "2025-06-07T14:44:38.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/8b/6c2cef44f8ccdc231f6b56013dff1d71138c48124334aded36b1a1b30c5a/numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb", size = 6640253, upload-time = "2025-06-07T14:44:49.359Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/aa/fca4bf8de3396ddb59544df9b75ffe5b73096174de97a9492d426f5cd4aa/numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0", size = 14258658, upload-time = "2025-06-07T14:45:10.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/12/734dce1087eed1875f2297f687e671cfe53a091b6f2f55f0c7241aad041b/numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f", size = 16628765, upload-time = "2025-06-07T14:45:35.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/03/ffa41ade0e825cbcd5606a5669962419528212a16082763fc051a7247d76/numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8", size = 15564335, upload-time = "2025-06-07T14:45:58.797Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/58/869398a11863310aee0ff85a3e13b4c12f20d032b90c4b3ee93c3b728393/numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270", size = 18360608, upload-time = "2025-06-07T14:46:25.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/8a/5756935752ad278c17e8a061eb2127c9a3edf4ba2c31779548b336f23c8d/numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f", size = 6310005, upload-time = "2025-06-07T14:50:13.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/60/61d60cf0dfc0bf15381eaef46366ebc0c1a787856d1db0c80b006092af84/numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5", size = 12729093, upload-time = "2025-06-07T14:50:31.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/31/2f2f2d2b3e3c32d5753d01437240feaa32220b73258c9eef2e42a0832866/numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e", size = 9885689, upload-time = "2025-06-07T14:50:47.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/89/c7828f23cc50f607ceb912774bb4cff225ccae7131c431398ad8400e2c98/numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8", size = 20986612, upload-time = "2025-06-07T14:46:56.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/46/79ecf47da34c4c50eedec7511e53d57ffdfd31c742c00be7dc1d5ffdb917/numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3", size = 14298953, upload-time = "2025-06-07T14:47:18.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/44/f6caf50713d6ff4480640bccb2a534ce1d8e6e0960c8f864947439f0ee95/numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f", size = 5225806, upload-time = "2025-06-07T14:47:27.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/43/e1fd1aca7c97e234dd05e66de4ab7a5be54548257efcdd1bc33637e72102/numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808", size = 6735169, upload-time = "2025-06-07T14:47:38.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/89/f76f93b06a03177c0faa7ca94d0856c4e5c4bcaf3c5f77640c9ed0303e1c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8", size = 14330701, upload-time = "2025-06-07T14:47:59.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/f5/4858c3e9ff7a7d64561b20580cf7cc5d085794bd465a19604945d6501f6c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad", size = 16692983, upload-time = "2025-06-07T14:48:24.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/17/0e3b4182e691a10e9483bcc62b4bb8693dbf9ea5dc9ba0b77a60435074bb/numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b", size = 15641435, upload-time = "2025-06-07T14:48:47.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/d5/463279fda028d3c1efa74e7e8d507605ae87f33dbd0543cf4c4527c8b882/numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555", size = 18433798, upload-time = "2025-06-07T14:49:14.866Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/1e/7a9d98c886d4c39a2b4d3a7c026bffcf8fbcaf518782132d12a301cfc47a/numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61", size = 6438632, upload-time = "2025-06-07T14:49:25.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/ab/66fc909931d5eb230107d016861824f335ae2c0533f422e654e5ff556784/numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb", size = 12868491, upload-time = "2025-06-07T14:49:44.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345, upload-time = "2025-06-07T14:50:02.311Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-mcp-server"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pytz" },
|
||||
{ name = "requests" },
|
||||
{ name = "reverse-geocoder" },
|
||||
{ name = "trafilatura" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = ">=0" },
|
||||
{ name = "pydantic", specifier = ">=2" },
|
||||
{ name = "pydantic-settings", specifier = ">=2" },
|
||||
{ name = "python-dateutil", specifier = ">=2" },
|
||||
{ name = "python-multipart", specifier = ">=0" },
|
||||
{ name = "pytz", specifier = ">=2025" },
|
||||
{ name = "requests", specifier = ">=2" },
|
||||
{ name = "reverse-geocoder", specifier = ">=1" },
|
||||
{ name = "trafilatura", specifier = ">=2" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ruff", specifier = ">=0" }]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reverse-geocoder"
|
||||
version = "1.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "scipy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/0f/b7d5d4b36553731f11983e19e1813a1059ad0732c5162c01b3220c927d31/reverse_geocoder-1.5.1.tar.gz", hash = "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c", size = 2246559, upload-time = "2016-09-15T16:46:46.277Z" }
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514", size = 4282054, upload-time = "2025-06-05T21:00:15.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46", size = 10292516, upload-time = "2025-06-05T20:59:32.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48", size = 11106083, upload-time = "2025-06-05T20:59:37.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b", size = 10436024, upload-time = "2025-06-05T20:59:39.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a", size = 10646324, upload-time = "2025-06-05T20:59:42.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc", size = 10174416, upload-time = "2025-06-05T20:59:44.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629", size = 11724197, upload-time = "2025-06-05T20:59:46.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933", size = 12511615, upload-time = "2025-06-05T20:59:49.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165", size = 12117080, upload-time = "2025-06-05T20:59:51.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71", size = 11326315, upload-time = "2025-06-05T20:59:54.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9", size = 11555640, upload-time = "2025-06-05T20:59:56.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc", size = 10507364, upload-time = "2025-06-05T20:59:59.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7", size = 10141462, upload-time = "2025-06-05T21:00:01.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432", size = 11121028, upload-time = "2025-06-05T21:00:04.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492", size = 11602992, upload-time = "2025-06-05T21:00:06.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250", size = 10474944, upload-time = "2025-06-05T21:00:08.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3", size = 11548669, upload-time = "2025-06-05T21:00:11.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.15.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.46.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tld"
|
||||
version = "0.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/a1/5723b07a70c1841a80afc9ac572fdf53488306848d844cd70519391b0d26/tld-0.13.1.tar.gz", hash = "sha256:75ec00936cbcf564f67361c41713363440b6c4ef0f0c1592b5b0fbe72c17a350", size = 462000, upload-time = "2025-05-21T22:18:29.341Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/70/b2f38360c3fc4bc9b5e8ef429e1fde63749144ac583c2dbdf7e21e27a9ad/tld-0.13.1-py2.py3-none-any.whl", hash = "sha256:a2d35109433ac83486ddf87e3c4539ab2c5c2478230e5d9c060a18af4b03aa7c", size = 274718, upload-time = "2025-05-21T22:18:25.811Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trafilatura"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "courlan" },
|
||||
{ name = "htmldate" },
|
||||
{ name = "justext" },
|
||||
{ name = "lxml" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/25/e3ebeefdebfdfae8c4a4396f5a6ea51fc6fa0831d63ce338e5090a8003dc/trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247", size = 253404, upload-time = "2024-12-03T15:23:24.16Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/b6/097367f180b6383a3581ca1b86fcae284e52075fa941d1232df35293363c/trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d", size = 132557, upload-time = "2024-12-03T15:23:21.41Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "5.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.34.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
standard = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "httptools" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
|
||||
{ name = "watchfiles" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue