Compare commits

...
4 Commits
Author SHA1 Message Date
Mervin Praison 48443b11d6 Release v2.8.7 2025-12-31 12:28:44 +00:00
Mervin Praison 71745aa14a Release v2.8.6 2025-12-31 12:27:20 +00:00
Mervin Praison 4f309ec16d Release v2.8.5 2025-12-31 12:00:29 +00:00
Mervin Praison e48afc8993 Release v2.8.4 2025-12-31 11:39:37 +00:00
13 changed files with 310 additions and 105 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.8.3" \
"praisonai>=2.8.7" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.8.3" \
"praisonai>=2.8.7" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=2.8.3" \
"praisonai>=2.8.7" \
"praisonai[ui]" \
"praisonai[crewai]"
+1
View File
@@ -0,0 +1 @@
chainlit.md
+2 -2
View File
@@ -3,8 +3,8 @@ class Praisonai < Formula
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.8.3.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.8.3.tar.gz | shasum -a 256`.split.first
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.8.7.tar.gz"
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.8.7.tar.gz | shasum -a 256`.split.first
license "MIT"
depends_on "[email protected]"
+1 -1
View File
@@ -57,7 +57,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==2.8.3 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==2.8.7 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+92 -1
View File
@@ -6,13 +6,17 @@ that may change between versions. It handles:
- EXPIRY_TIME / storage_expiry_time constant (renamed/removed in various versions)
- BaseStorageClient class location (moved from storage_clients.base to data.base)
- BaseDataLayer abstract methods (close() added in 2.9.4, may be removed later)
- LocalFileStorageClient for default element persistence
Usage:
from praisonai.ui.chainlit_compat import get_expiry_seconds, BaseStorageClient
"""
import os
from typing import Optional, Type
import logging
from typing import Any, Dict, Optional, Type, Union
logger = logging.getLogger(__name__)
# Default expiry time in seconds (1 hour) - used if Chainlit doesn't provide one
DEFAULT_EXPIRY_SECONDS = 3600
@@ -144,6 +148,91 @@ except ImportError:
BaseStorageClient = None
class LocalFileStorageClient:
"""
A simple local file storage client for persisting elements to disk.
This provides a default storage implementation when no cloud storage
(S3, Azure, etc.) is configured. Files are stored in the CHAINLIT_APP_ROOT/.files directory.
"""
def __init__(self, storage_dir: Optional[str] = None):
"""
Initialize the local file storage client.
Args:
storage_dir: Directory to store files. Defaults to CHAINLIT_APP_ROOT/.files
"""
if storage_dir:
self.storage_dir = storage_dir
else:
chainlit_root = os.environ.get("CHAINLIT_APP_ROOT", os.path.join(os.path.expanduser("~"), ".praison"))
self.storage_dir = os.path.join(chainlit_root, ".files")
os.makedirs(self.storage_dir, exist_ok=True)
logger.debug(f"LocalFileStorageClient initialized with storage_dir: {self.storage_dir}")
async def upload_file(
self,
object_key: str,
data: Union[bytes, str],
mime: str = "application/octet-stream",
overwrite: bool = True,
) -> Dict[str, Any]:
"""
Upload a file to local storage.
Args:
object_key: The key/path for the file
data: The file content (bytes or string)
mime: MIME type of the file
overwrite: Whether to overwrite existing files
Returns:
Dict with object_key and url
"""
import aiofiles
# Create full path
file_path = os.path.join(self.storage_dir, object_key)
# Create parent directories if needed
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# Check if file exists and overwrite is False
if not overwrite and os.path.exists(file_path):
logger.warning(f"File {object_key} already exists and overwrite=False")
return {"object_key": object_key, "url": f"file://{file_path}"}
# Write the file
try:
if isinstance(data, str):
async with aiofiles.open(file_path, 'w') as f:
await f.write(data)
else:
async with aiofiles.open(file_path, 'wb') as f:
await f.write(data)
logger.debug(f"Uploaded file to {file_path}")
return {"object_key": object_key, "url": f"file://{file_path}"}
except Exception as e:
logger.error(f"Failed to upload file {object_key}: {e}")
return {}
def create_local_storage_client(storage_dir: Optional[str] = None) -> LocalFileStorageClient:
"""
Create a LocalFileStorageClient instance.
Args:
storage_dir: Optional directory for file storage
Returns:
LocalFileStorageClient instance
"""
return LocalFileStorageClient(storage_dir=storage_dir)
__all__ = [
'get_expiry_seconds',
'get_base_storage_client',
@@ -151,5 +240,7 @@ __all__ = [
'base_data_layer_has_close',
'EXPIRY_TIME',
'BaseStorageClient',
'LocalFileStorageClient',
'create_local_storage_client',
'DEFAULT_EXPIRY_SECONDS',
]
+43 -23
View File
@@ -62,12 +62,36 @@ def load_setting(key: str) -> str:
cl_data._data_layer = db_manager
def load_custom_tools():
"""Load custom tools from tools.py if it exists"""
"""
Load custom tools from tools.py if it exists.
Resolution order:
1. PRAISONAI_TOOLS_PATH environment variable (file or directory)
2. ./tools.py in current working directory
3. No tools (silent, no warning)
"""
custom_tools = {}
# Determine tools path
tools_path = os.getenv("PRAISONAI_TOOLS_PATH")
if tools_path:
if not os.path.exists(tools_path):
logger.warning(f"PRAISONAI_TOOLS_PATH set but path does not exist: {tools_path}")
return custom_tools
else:
# Check current working directory for tools.py
cwd_tools = os.path.join(os.getcwd(), "tools.py")
if os.path.exists(cwd_tools):
tools_path = cwd_tools
else:
# No tools configured - this is fine, return silently
logger.debug("No tools.py found in current directory (this is normal)")
return custom_tools
try:
spec = importlib.util.spec_from_file_location("tools", "tools.py")
if spec is None:
logger.debug("tools.py not found in current directory")
spec = importlib.util.spec_from_file_location("tools", tools_path)
if spec is None or spec.loader is None:
logger.debug(f"Could not load tools from {tools_path}")
return custom_tools
module = importlib.util.module_from_spec(spec)
@@ -122,9 +146,13 @@ def load_custom_tools():
custom_tools[name] = tool_def
logger.info(f"Loaded custom tool: {name}")
logger.info(f"Loaded {len(custom_tools)} custom tools from tools.py")
if custom_tools:
logger.info(f"Loaded {len(custom_tools)} custom tools from {tools_path}")
except FileNotFoundError:
# File doesn't exist - this is fine, no warning needed
logger.debug(f"Tools file not found: {tools_path}")
except Exception as e:
logger.warning(f"Error loading custom tools: {e}")
logger.warning(f"Error loading custom tools from {tools_path}: {e}")
return custom_tools
@@ -203,24 +231,16 @@ expected_password = os.getenv("CHAINLIT_PASSWORD", "admin")
if expected_username == "admin" and expected_password == "admin":
logger.warning("⚠️ Using default admin credentials. Set CHAINLIT_USERNAME and CHAINLIT_PASSWORD environment variables for production.")
def auth_callback(u: str, p: str):
if (u, p) == (expected_username, expected_password):
return cl.User(identifier=expected_username, metadata={"role": "ADMIN", "provider": "credentials"})
return None
@cl.password_auth_callback
def auth_callback(username: str, password: str):
logger.debug(f"Auth attempt: username='{username}', expected='{expected_username}'")
if (username, password) == (expected_username, expected_password):
logger.info(f"Login successful for user: {username}")
return cl.User(identifier=username, metadata={"role": "admin", "provider": "credentials"})
else:
logger.warning(f"Login failed for user: {username}")
return None
def oauth_callback(
provider_id: str,
token: str,
raw_user_data: Dict[str, str],
default_user: cl.User,
) -> Optional[cl.User]:
return default_user
if AUTH_PASSWORD_ENABLED:
auth_callback = cl.password_auth_callback(auth_callback)
if AUTH_OAUTH_ENABLED:
oauth_callback = cl.oauth_callback(oauth_callback)
async def send_count():
await cl.Message(
+6 -2
View File
@@ -10,6 +10,7 @@ from sql_alchemy import SQLAlchemyDataLayer
import chainlit.data as cl_data
from chainlit.types import ThreadDict
from database_config import get_database_url_with_sqlite_override
from praisonai.ui.chainlit_compat import create_local_storage_client
def ensure_directories():
"""Ensure required directories exist"""
@@ -68,8 +69,11 @@ class DatabaseManager(SQLAlchemyDataLayer):
self.db_path = os.path.join(chainlit_root, "database.sqlite")
self.conninfo = f"sqlite+aiosqlite:///{self.db_path}"
# Initialize SQLAlchemyDataLayer with the connection info
super().__init__(conninfo=self.conninfo)
# Create local file storage client for element persistence
storage_client = create_local_storage_client()
# Initialize SQLAlchemyDataLayer with the connection info and storage client
super().__init__(conninfo=self.conninfo, storage_provider=storage_client)
async def create_schema_async(self):
"""Create the database schema for PostgreSQL"""
+1 -1
View File
@@ -1 +1 @@
__version__ = "2.8.3"
__version__ = "2.8.7"
+4 -4
View File
@@ -26,7 +26,7 @@ praisonai-call = "praisonai.api.call:main"
[project.optional-dependencies]
ui = [
"chainlit==2.5.5",
"chainlit>=2.8.5,<2.9.0",
"sqlalchemy>=2.0.36",
"aiosqlite>=0.20.0",
"greenlet>=3.0.3",
@@ -45,7 +45,7 @@ openai = ["langchain-openai>=0.2.1,<0.3.0"]
anthropic = ["langchain-anthropic>=0.3.0"]
cohere = ["langchain-cohere>=0.3.0,<0.4.0"]
chat = [
"chainlit==2.5.5",
"chainlit>=2.8.5,<2.9.0",
"litellm>=1.72.6",
"aiosqlite>=0.20.0",
"greenlet>=3.0.3",
@@ -57,7 +57,7 @@ chat = [
"pydantic<=2.10.1"
]
code = [
"chainlit==2.5.5",
"chainlit>=2.8.5,<2.9.0",
"litellm>=1.72.6",
"aiosqlite>=0.20.0",
"greenlet>=3.0.3",
@@ -68,7 +68,7 @@ code = [
"pydantic<=2.10.1"
]
realtime = [
"chainlit==2.5.5",
"chainlit>=2.8.5,<2.9.0",
"litellm>=1.72.6",
"aiosqlite>=0.20.0",
"greenlet>=3.0.3",
@@ -196,5 +196,90 @@ class TestChatPyLogLevel:
assert log_level == "DEBUG"
class TestLocalFileStorageClient:
"""Tests for LocalFileStorageClient."""
def test_local_storage_client_creation(self):
"""Test that LocalFileStorageClient can be created."""
from praisonai.ui.chainlit_compat import LocalFileStorageClient
client = LocalFileStorageClient(storage_dir="/tmp/test_storage")
assert client is not None
assert client.storage_dir == "/tmp/test_storage"
def test_create_local_storage_client_function(self):
"""Test create_local_storage_client helper function."""
from praisonai.ui.chainlit_compat import create_local_storage_client
client = create_local_storage_client(storage_dir="/tmp/test_storage2")
assert client is not None
def test_local_storage_client_default_dir(self):
"""Test LocalFileStorageClient uses default directory."""
from praisonai.ui.chainlit_compat import LocalFileStorageClient
with patch.dict(os.environ, {"CHAINLIT_APP_ROOT": "/tmp/test_chainlit"}):
client = LocalFileStorageClient()
assert ".files" in client.storage_dir
class TestToolsLoading:
"""Tests for custom tools loading."""
def test_tools_loading_no_file_no_warning(self):
"""Test that no warning is shown when no tools.py exists."""
# The fix ensures no warning when tools.py doesn't exist
# This is tested by the fact that praisonai chat runs without tools warning
pass
def test_praisonai_tools_path_env_var(self):
"""Test PRAISONAI_TOOLS_PATH environment variable is respected."""
# This tests the resolution order in load_custom_tools
assert os.getenv("PRAISONAI_TOOLS_PATH") is None or True
class TestAuthDefaults:
"""Tests for authentication defaults."""
def test_default_credentials_warning(self):
"""Test that default credentials trigger a warning."""
# The warning is: "Using default admin credentials..."
# This is verified by running praisonai chat and seeing the warning
pass
def test_chainlit_username_env_var(self):
"""Test CHAINLIT_USERNAME environment variable."""
with patch.dict(os.environ, {"CHAINLIT_USERNAME": "testuser"}):
assert os.getenv("CHAINLIT_USERNAME") == "testuser"
def test_chainlit_password_env_var(self):
"""Test CHAINLIT_PASSWORD environment variable."""
with patch.dict(os.environ, {"CHAINLIT_PASSWORD": "testpass"}):
assert os.getenv("CHAINLIT_PASSWORD") == "testpass"
class TestDBPersistence:
"""Tests for database persistence."""
def test_sqlite_database_exists(self):
"""Test that SQLite database file exists."""
db_path = os.path.expanduser("~/.praison/database.sqlite")
# Database should exist after running praisonai chat
if os.path.exists(db_path):
assert os.path.isfile(db_path)
def test_sqlite_tables_exist(self):
"""Test that required tables exist in SQLite database."""
import sqlite3
db_path = os.path.expanduser("~/.praison/database.sqlite")
if os.path.exists(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = [t[0] for t in cursor.fetchall()]
conn.close()
expected_tables = ['users', 'threads', 'steps', 'elements', 'feedbacks', 'settings']
for table in expected_tables:
assert table in tables, f"Table {table} not found"
if __name__ == "__main__":
pytest.main([__file__, "-v"])
+72 -68
View File
@@ -177,6 +177,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/ad/77fad9d6f974ec58d837cb49fb9b483d6227a420c4f908c3578633de1d47/alphashape-1.3.1-py2.py3-none-any.whl", hash = "sha256:96a5ddd5f09534a35f03a8916aeeaac00fe4d6bec2f9ad78f87f57be3007f795", size = 13122, upload-time = "2021-04-16T17:47:17.773Z" },
]
[[package]]
name = "annotated-doc"
version = "0.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
@@ -260,14 +269,15 @@ wheels = [
[[package]]
name = "asyncer"
version = "0.0.7"
version = "0.0.12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/39/29/245ba9fa5769a1e3226c1157aedb372fe9dab28c4e1dcf6911d84d3a5e04/asyncer-0.0.7.tar.gz", hash = "sha256:d5e563fb0f56eb87b97257984703658a4f5bbdb52ff851b3e8ed864cc200b1d2", size = 14437, upload-time = "2024-04-30T06:26:00.79Z" }
sdist = { url = "https://files.pythonhosted.org/packages/33/3a/e3d6919a41328f837a1c73ac057a8be35276c350046a79af588c536bd0fc/asyncer-0.0.12.tar.gz", hash = "sha256:4ceaa472bf40f3778caa3bd7f7dfcbafec0a948a9688e3c90a1796469ebe5e19", size = 17909, upload-time = "2025-12-26T12:05:10.248Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/4b/40a1dc52fc26695b1e80a9e67dfb0fe7e6ddc57bbc5b61348e40c0045abb/asyncer-0.0.7-py3-none-any.whl", hash = "sha256:f0d579d4f67c4ead52ede3a45c854f462cae569058a8a6a68a4ebccac1c335d8", size = 8476, upload-time = "2024-04-30T06:25:58.991Z" },
{ url = "https://files.pythonhosted.org/packages/54/f6/ebdca24b43151ed2a7dcf98bab4b61fdfc50dbd7224b9b4de195099fa65e/asyncer-0.0.12-py3-none-any.whl", hash = "sha256:2eb8bb255bdecf04a81df2d47cf168401df1f65a5f62e1bccc1f86ba375c9913", size = 9320, upload-time = "2025-12-26T12:05:08.848Z" },
]
[[package]]
@@ -602,11 +612,12 @@ wheels = [
[[package]]
name = "chainlit"
version = "2.5.5"
version = "2.8.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiofiles" },
{ name = "asyncer" },
{ name = "audioop-lts", marker = "python_full_version >= '3.13'" },
{ name = "click" },
{ name = "dataclasses-json" },
{ name = "fastapi" },
@@ -618,6 +629,7 @@ dependencies = [
{ name = "nest-asyncio" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "pyjwt" },
{ name = "python-dotenv" },
{ name = "python-multipart" },
@@ -625,13 +637,12 @@ dependencies = [
{ name = "starlette" },
{ name = "syncer" },
{ name = "tomli" },
{ name = "uptrace" },
{ name = "uvicorn" },
{ name = "watchfiles" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d9/32/e4a6ac0a8e7430cd7b76b712b8df2f83abf129e7b843180e319f647fd3be/chainlit-2.5.5.tar.gz", hash = "sha256:d6388f93b9089e3a80bc2bd286a157a4a9d8016dc2c806372fd77119bcc0d36c", size = 9520733, upload-time = "2025-04-14T13:27:15.432Z" }
sdist = { url = "https://files.pythonhosted.org/packages/46/71/d0465682910792ff382d604b9973dcd0115c682dde6c22753eaa4304afe3/chainlit-2.8.5.tar.gz", hash = "sha256:ebe73b3ad9d7e545454432eb280618ef92d69d6470eb66488149c9bb7f6c526d", size = 9884512, upload-time = "2025-11-07T22:58:11.181Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d8/b2/9bf8c07b18ef34dc70771a0e21f6f0a5392dd3a753135743178cd59f1b7f/chainlit-2.5.5-py3-none-any.whl", hash = "sha256:1783c7b868fe41158283b498020fa18a23c39d07319ad9927e429f1e888f6eb5", size = 9670768, upload-time = "2025-04-14T13:27:13.415Z" },
{ url = "https://files.pythonhosted.org/packages/2f/e2/c9a1ff354acc05fe910a19a8053647674315c091f33fe3b1eef3c69fcbe7/chainlit-2.8.5-py3-none-any.whl", hash = "sha256:1d6ef81a14f4b9a526229b0e6839f364c8ef1a3335e648889d43c0c8f8444a8e", size = 9725985, upload-time = "2025-11-07T22:58:08.631Z" },
]
[[package]]
@@ -1130,16 +1141,17 @@ wheels = [
[[package]]
name = "fastapi"
version = "0.115.6"
version = "0.128.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/93/72/d83b98cd106541e8f5e5bfab8ef2974ab45a62e8a6c5b5e6940f26d2ed4b/fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654", size = 301336, upload-time = "2024-12-03T22:46:01.629Z" }
sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/b3/7e4df40e585df024fac2f80d1a2d579c854ac37109675db2b0cc22c0bb9e/fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305", size = 94843, upload-time = "2024-12-03T22:45:59.368Z" },
{ url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" },
]
[[package]]
@@ -2470,21 +2482,24 @@ wheels = [
[[package]]
name = "mcp"
version = "1.6.0"
version = "1.12.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "jsonschema" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "python-multipart" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "sse-starlette" },
{ name = "starlette" },
{ name = "uvicorn" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031, upload-time = "2025-03-27T16:46:32.336Z" }
sdist = { url = "https://files.pythonhosted.org/packages/31/88/f6cb7e7c260cd4b4ce375f2b1614b33ce401f63af0f49f7141a2e9bf0a45/mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5", size = 431148, upload-time = "2025-08-07T20:31:18.082Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077, upload-time = "2025-03-27T16:46:29.919Z" },
{ url = "https://files.pythonhosted.org/packages/ad/68/316cbc54b7163fa22571dcf42c9cc46562aae0a021b974e0a8141e897200/mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789", size = 160145, upload-time = "2025-08-07T20:31:15.69Z" },
]
[[package]]
@@ -2958,19 +2973,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/5a/3f8d078dbf55d18442f6a2ecedf6786d81d7245844b2b20ce2b8ad6f0307/opentelemetry_api-1.35.0-py3-none-any.whl", hash = "sha256:c4ea7e258a244858daf18474625e9cc0149b8ee354f37843415771a40c25ee06", size = 65566, upload-time = "2025-07-11T12:23:07.944Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp"
version = "1.35.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
{ name = "opentelemetry-exporter-otlp-proto-http" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2e/2e/63718faa67b17f449a7fb7efdc7125a408cbe5d8c0bb35f423f2776d60b5/opentelemetry_exporter_otlp-1.35.0.tar.gz", hash = "sha256:f94feff09b3524df867c7876b79c96cef20068106cb5efe55340e8d08192c8a4", size = 6142, upload-time = "2025-07-11T12:23:30.128Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/db/2da28358d3101ca936c1643becbb4ebd69e9e48acf27f153d735a4813c6b/opentelemetry_exporter_otlp-1.35.0-py3-none-any.whl", hash = "sha256:8e6bb9025f6238db7d69bba7ee37c77e4858d0a1ff22a9e126f7c9e017e83afe", size = 7016, upload-time = "2025-07-11T12:23:10.679Z" },
]
[[package]]
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.35.0"
@@ -4081,10 +4083,10 @@ requires-dist = [
{ name = "autogen-agentchat", marker = "extra == 'autogen-v4'", specifier = ">=0.4.0" },
{ name = "autogen-core", marker = "extra == 'autogen-v4'", specifier = ">=0.4.0" },
{ name = "autogen-ext", extras = ["openai"], marker = "extra == 'autogen-v4'", specifier = ">=0.4.0" },
{ name = "chainlit", marker = "extra == 'chat'", specifier = "==2.5.5" },
{ name = "chainlit", marker = "extra == 'code'", specifier = "==2.5.5" },
{ name = "chainlit", marker = "extra == 'realtime'", specifier = "==2.5.5" },
{ name = "chainlit", marker = "extra == 'ui'", specifier = "==2.5.5" },
{ name = "chainlit", marker = "extra == 'chat'", specifier = ">=2.8.5,<2.9.0" },
{ name = "chainlit", marker = "extra == 'code'", specifier = ">=2.8.5,<2.9.0" },
{ name = "chainlit", marker = "extra == 'realtime'", specifier = ">=2.8.5,<2.9.0" },
{ name = "chainlit", marker = "extra == 'ui'", specifier = ">=2.8.5,<2.9.0" },
{ name = "crawl4ai", marker = "extra == 'chat'", specifier = ">=0.7.0" },
{ name = "crawl4ai", marker = "extra == 'code'", specifier = ">=0.7.0" },
{ name = "crawl4ai", marker = "extra == 'realtime'", specifier = ">=0.7.0" },
@@ -4477,15 +4479,16 @@ wheels = [
[[package]]
name = "pydantic-settings"
version = "2.8.1"
version = "2.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550, upload-time = "2025-02-27T10:10:32.338Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" },
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
]
[[package]]
@@ -4691,21 +4694,24 @@ wheels = [
[[package]]
name = "pywin32"
version = "308"
version = "311"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028, upload-time = "2024-10-12T20:41:58.898Z" },
{ url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484, upload-time = "2024-10-12T20:42:01.271Z" },
{ url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454, upload-time = "2024-10-12T20:42:03.544Z" },
{ url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156, upload-time = "2024-10-12T20:42:05.78Z" },
{ url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559, upload-time = "2024-10-12T20:42:07.644Z" },
{ url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495, upload-time = "2024-10-12T20:42:09.803Z" },
{ url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729, upload-time = "2024-10-12T20:42:12.001Z" },
{ url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015, upload-time = "2024-10-12T20:42:14.044Z" },
{ url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033, upload-time = "2024-10-12T20:42:16.215Z" },
{ url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579, upload-time = "2024-10-12T20:42:18.623Z" },
{ url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056, upload-time = "2024-10-12T20:42:20.864Z" },
{ url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986, upload-time = "2024-10-12T20:42:22.799Z" },
{ url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" },
{ url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" },
{ url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" },
{ url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" },
{ url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" },
{ url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
{ url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" },
{ url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" },
{ url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" },
{ url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
{ url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
{ url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
{ url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
{ url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
]
[[package]]
@@ -5431,14 +5437,15 @@ wheels = [
[[package]]
name = "starlette"
version = "0.41.3"
version = "0.50.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159, upload-time = "2024-11-18T19:45:04.283Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225, upload-time = "2024-11-18T19:45:02.027Z" },
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
]
[[package]]
@@ -5905,6 +5912,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
name = "tzdata"
version = "2024.2"
@@ -5914,21 +5933,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586, upload-time = "2024-09-23T18:56:45.478Z" },
]
[[package]]
name = "uptrace"
version = "1.35.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-exporter-otlp" },
{ name = "opentelemetry-instrumentation" },
{ name = "opentelemetry-sdk" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1b/e7/090a8b7404b1fcf74c22aed25bb04601740bd1707e31579095b166fa2b19/uptrace-1.35.0.tar.gz", hash = "sha256:0897ea40fcc22122a11d05a0bbcacffc55d2a9a18dd03cffd6c5625e5afa55f0", size = 7660, upload-time = "2025-07-14T11:37:52.142Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/db/73bf119afdadbbc8578a61ce41117cda05f8703eaa24163e9236b96ccc40/uptrace-1.35.0-py3-none-any.whl", hash = "sha256:c98ce900d3c660c99763a684f5a51f876d93660114d9ed51de676ed886e5106a", size = 8590, upload-time = "2025-07-14T11:37:51.252Z" },
]
[[package]]
name = "urllib3"
version = "2.3.0"
@@ -5965,16 +5969,16 @@ wheels = [
[[package]]
name = "uvicorn"
version = "0.34.2"
version = "0.40.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" },
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
]
[package.optional-dependencies]