├── .gitignore ├── .python-version ├── README.md ├── __init__.py ├── pyproject.toml ├── run_mcp_server.py ├── src └── mcp_email_client │ ├── __init__.py │ ├── __main__.py │ ├── config.py │ ├── config │ └── .gitignore │ ├── db.py │ ├── mailhandler.py │ ├── semantic.py │ └── server.py ├── template └── .gitignore ├── tests ├── test_db.py ├── test_mail.py ├── test_mcp_server.py └── test_semantic.py └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | .ropeproject 9 | 10 | # Virtual environments 11 | .venv 12 | .vscode 13 | config/*.json 14 | !config/new_name.json 15 | emails.duckdb -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mail Client MCP 2 | 3 | ## Overview 4 | 5 | Mail Client MCP is a Python-based email client that allows users to manage email configurations, send emails, and read the latest unread emails. It provide MCP for Claude Desktop 6 | 7 | ## Features 8 | 9 | - List all email configurations 10 | - Add new email configurations 11 | - Update existing email configurations 12 | - Delete email configurations 13 | - Send emails using specified configurations 14 | - Read the latest 5 unread emails 15 | 16 | ## Installation 17 | 18 | 1. Clone the repository: 19 | ```sh 20 | git clone https://github.com/gamalan/mcp-email-client.git 21 | cd mcp-email-client 22 | ``` 23 | 2. Install uv 24 | Linux/MacOS 25 | ```sh 26 | curl -LsSf https://astral.sh/uv/install.sh | sh 27 | ``` 28 | Windows 29 | ```powershell 30 | powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 31 | ``` 32 | 33 | 3. Install dependencies: 34 | ```sh 35 | uv sync 36 | ``` 37 | 38 | ## Configuration 39 | 40 | Configuration example using Claude Desktop 41 | ```json 42 | { 43 | "mcpServers": { 44 | "mcp_email_client": { 45 | "command": "uv", 46 | "args": [ 47 | "run", 48 | "--directory", 49 | "D:\\Project\\RepoPath", 50 | "mcp_email_client" 51 | ] 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | or in VsCode 58 | 59 | ```json 60 | { 61 | "servers": { 62 | "any-name": { 63 | "type": "stdio", 64 | "command": "/path/to/uv", 65 | "args": [ 66 | "run", 67 | "--directory", 68 | "/path/to/repo", 69 | "run_mcp_server.py", 70 | ] 71 | } 72 | } 73 | } 74 | ``` -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalan/mcp-email-client/51e431a777cbc10ec8aecd9507d28e330e1d3247/__init__.py -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mcp-email-client" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "asyncio>=3.4.3", 9 | "duckdb>=1.2.2", 10 | "imapclient>=3.0.1", 11 | "mcp[cli]>=1.3.0", 12 | "numpy>=2.2.4", 13 | "pydantic>=2.10.6", 14 | "sentence-transformers>=4.1.0", 15 | ] 16 | 17 | [project.scripts] 18 | mcp_email_client = "mcp_email_client:main" 19 | 20 | [build-system] 21 | requires = ["hatchling"] 22 | build-backend = "hatchling.build" 23 | -------------------------------------------------------------------------------- /run_mcp_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | MCP Server Launcher for testing 4 | 5 | This script starts the MCP server for interactive testing. 6 | Run it from VS Code to test the MCP server functionality. 7 | """ 8 | import asyncio 9 | import logging 10 | import sys 11 | from pathlib import Path 12 | import os 13 | 14 | # Add the current directory to the path to ensure modules can be found 15 | sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) 16 | 17 | from mcp_email_client.server import serve 18 | 19 | # Configure logging 20 | logging.basicConfig( 21 | level=logging.DEBUG, 22 | format='%(asctime)s [%(levelname)s] %(message)s', 23 | handlers=[ 24 | logging.StreamHandler(sys.stdout) 25 | ] 26 | ) 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | async def main(): 31 | try: 32 | logger.info("Starting MCP Email server...") 33 | 34 | # Get the current directory as the repository path 35 | repo_path = Path(os.path.dirname(os.path.abspath(__file__))) 36 | 37 | # Run the server 38 | await serve(repo_path) 39 | except Exception as e: 40 | logger.error(f"Error starting MCP server: {str(e)}") 41 | raise 42 | 43 | if __name__ == "__main__": 44 | asyncio.run(main()) -------------------------------------------------------------------------------- /src/mcp_email_client/__init__.py: -------------------------------------------------------------------------------- 1 | from . import server 2 | import asyncio 3 | 4 | def main(): 5 | """Main entry point for the package.""" 6 | asyncio.run(server.main()) 7 | 8 | # Optionally expose other important items at package level 9 | __all__ = ["main", "server"] -------------------------------------------------------------------------------- /src/mcp_email_client/__main__.py: -------------------------------------------------------------------------------- 1 | from mcp_email_client.server import main 2 | 3 | if __name__ == "__main__": 4 | main() -------------------------------------------------------------------------------- /src/mcp_email_client/config.py: -------------------------------------------------------------------------------- 1 | import json, os 2 | 3 | class MailConfig: 4 | def __init__(self, _name: str, inbound_host: str, inbound_port: int, inbound_user: str, inbound_password: str, inbound_ssl: str="SSL/TLS", is_outbound_equal: bool=True, outbound_host: str="", outbound_port: int=0, outbound_user: str="", outbound_password: str="", outbound_ssl: str=""): 5 | self._name = _name 6 | self.inbound_host = inbound_host 7 | self.inbound_port = inbound_port 8 | self.inbound_user = inbound_user 9 | self.inbound_password = inbound_password 10 | self.inbound_ssl = inbound_ssl 11 | self.is_outbound_equal = is_outbound_equal 12 | if self.is_outbound_equal: 13 | self.outbound_host = self.inbound_host 14 | self.outbound_port = self.inbound_port 15 | self.outbound_user = self.inbound_user 16 | self.outbound_password = self.inbound_password 17 | self.outbound_ssl = self.inbound_ssl 18 | else: 19 | self.outbound_host = outbound_host 20 | self.outbound_port = outbound_port 21 | self.outbound_user = outbound_user 22 | self.outbound_password = outbound_password 23 | self.outbound_ssl = outbound_ssl 24 | self.config_file = os.path.join(os.path.dirname(__file__),"config", self.name +'.json') 25 | 26 | @property 27 | def name(self): 28 | return self._name 29 | 30 | @name.setter 31 | def name(self, value: str): 32 | self._name = value 33 | self.config_file = os.path.join(os.path.dirname(__file__),"config", value +'.json') 34 | 35 | def save_entry(self): 36 | with open(self.config_file, 'w') as f: 37 | json.dump(self.__dict__, f) 38 | 39 | def update(self, **kwargs): 40 | for key, value in kwargs.items(): 41 | setattr(self, key, value) 42 | self.save_entry() 43 | 44 | def __str__(self): 45 | return f"MailConfig(name={self._name}, inbound_host={self.inbound_host}, inbound_port={self.inbound_port}, inbound_user={self.inbound_user}, inbound_password={self.inbound_password}, inbound_ssl={self.inbound_ssl}, outbound_host={self.outbound_host}, outbound_port={self.outbound_port}, outbound_user={self.outbound_user}, outbound_password={self.outbound_password}, outbound_ssl={self.outbound_ssl})" 46 | 47 | @staticmethod 48 | def load_entry(name): 49 | with open(os.path.join(os.path.dirname(__file__),"config", name + '.json'), 'r') as f: 50 | data = json.load(f) 51 | del data['config_file'] 52 | return MailConfig(**data) 53 | 54 | @staticmethod 55 | def delete_entry(name:str): 56 | return os.remove(os.path.join(os.path.dirname(__file__),"config", name + '.json')) 57 | 58 | @staticmethod 59 | def load_all(): 60 | configs = [] 61 | for file in os.listdir(os.path.join(os.path.dirname(__file__),"config")): 62 | if file.endswith('.json'): 63 | with open(os.path.join(os.path.dirname(__file__),"config", file), 'r') as f: 64 | data = json.load(f) 65 | del data['config_file'] 66 | configs.append(MailConfig(**data)) 67 | return configs 68 | 69 | 70 | #mail_config = MailConfig("default", "smtp.example.com", 587, "user@example.com", "password", "SSL/TLS", False, "smtp.example.com", 587, "user@example.com", "password", "SSL/TLS") 71 | #mail_config.save_entry() 72 | #mail_config.name = "new_name" 73 | #mail_config.save_entry() 74 | #MailConfig.delete_entry("default") 75 | #mail_config = MailConfig.load_entry("new_name") 76 | #print(mail_config) 77 | #mail_config.update(outbound_host="smtp.example.com", outbound_port=465, outbound_user="user@example.com", outbound_password="password", outbound_ssl="SSL/TLS") 78 | #print(mail_config) 79 | -------------------------------------------------------------------------------- /src/mcp_email_client/config/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /src/mcp_email_client/db.py: -------------------------------------------------------------------------------- 1 | import duckdb 2 | import os 3 | import json 4 | import datetime 5 | from pathlib import Path 6 | from typing import List, Dict, Any, Optional 7 | 8 | # Define database file path 9 | DB_FILE = Path("emails.duckdb") 10 | 11 | def init_db() -> None: 12 | """Initialize the database and create tables if they don't exist.""" 13 | with duckdb.connect(str(DB_FILE)) as conn: 14 | # Create emails table with IDENTITY for auto-incrementing ID 15 | conn.execute(""" 16 | CREATE SEQUENCE IF NOT EXISTS email_id_seq START 1; 17 | """) 18 | 19 | # Create emails table 20 | conn.execute(""" 21 | CREATE TABLE IF NOT EXISTS emails ( 22 | id INTEGER PRIMARY KEY, 23 | config_name VARCHAR, 24 | subject VARCHAR, 25 | body TEXT, 26 | sender VARCHAR, 27 | recipients VARCHAR, 28 | cc VARCHAR, 29 | bcc VARCHAR, 30 | date TIMESTAMP, 31 | raw_content TEXT, 32 | embedding BLOB -- For storing vector embeddings later 33 | ) 34 | """) 35 | 36 | def insert_email( 37 | config_name: str, 38 | subject: str, 39 | body: str, 40 | sender: str = None, 41 | recipients: str = None, 42 | cc: str = None, 43 | bcc: str = None, 44 | date: datetime.datetime = None, 45 | raw_content: str = None 46 | ) -> int: 47 | """Insert an email into the database.""" 48 | if date is None: 49 | date = datetime.datetime.now() 50 | 51 | with duckdb.connect(str(DB_FILE)) as conn: 52 | try: 53 | # Get the next ID from sequence 54 | result = conn.execute("SELECT nextval('email_id_seq')").fetchone() 55 | next_id = result[0] if result else 1 56 | 57 | # Insert the email with explicit ID 58 | conn.execute(""" 59 | INSERT INTO emails (id, config_name, subject, body, sender, recipients, cc, bcc, date, raw_content) 60 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 61 | """, (next_id, config_name, subject, body, sender, recipients, cc, bcc, date, raw_content)) 62 | 63 | return next_id 64 | except Exception as e: 65 | # Handle the error more gracefully 66 | print(f"Error inserting email: {e}") 67 | # If the error is about the sequence not existing, create it and try again 68 | if "does not exist" in str(e): 69 | conn.execute("CREATE SEQUENCE IF NOT EXISTS email_id_seq START 1") 70 | return insert_email(config_name, subject, body, sender, recipients, cc, bcc, date, raw_content) 71 | raise 72 | 73 | def get_emails( 74 | config_name: Optional[str] = None, 75 | limit: int = 10, 76 | offset: int = 0 77 | ) -> List[Dict[str, Any]]: 78 | """Get emails from database with optional filtering by config_name.""" 79 | with duckdb.connect(str(DB_FILE)) as conn: 80 | query = "SELECT * FROM emails" 81 | params = [] 82 | 83 | if config_name: 84 | query += " WHERE config_name = ?" 85 | params.append(config_name) 86 | 87 | query += " ORDER BY date DESC LIMIT ? OFFSET ?" 88 | params.extend([limit, offset]) 89 | 90 | result = conn.execute(query, params).fetchall() 91 | columns = [desc[0] for desc in conn.description] 92 | 93 | return [dict(zip(columns, row)) for row in result] 94 | 95 | def get_emails_by_date_range( 96 | start_date: datetime.datetime, 97 | end_date: datetime.datetime = None, 98 | config_name: Optional[str] = None, 99 | limit: int = 100 100 | ) -> List[Dict[str, Any]]: 101 | """Get emails within a specified date range.""" 102 | if end_date is None: 103 | end_date = datetime.datetime.now() 104 | 105 | with duckdb.connect(str(DB_FILE)) as conn: 106 | query = "SELECT * FROM emails WHERE date BETWEEN ? AND ?" 107 | params = [start_date, end_date] 108 | 109 | if config_name: 110 | query += " AND config_name = ?" 111 | params.append(config_name) 112 | 113 | query += " ORDER BY date DESC LIMIT ?" 114 | params.append(limit) 115 | 116 | result = conn.execute(query, params).fetchall() 117 | columns = [desc[0] for desc in conn.description] 118 | 119 | return [dict(zip(columns, row)) for row in result] 120 | 121 | def get_all_emails( 122 | config_name: Optional[str] = None, 123 | limit: int = 1000 124 | ) -> List[Dict[str, Any]]: 125 | """Get all emails from the database with optional config_name filtering.""" 126 | with duckdb.connect(str(DB_FILE)) as conn: 127 | query = "SELECT * FROM emails" 128 | params = [] 129 | 130 | if config_name: 131 | query += " WHERE config_name = ?" 132 | params.append(config_name) 133 | 134 | query += " ORDER BY date DESC LIMIT ?" 135 | params.append(limit) 136 | 137 | result = conn.execute(query, params).fetchall() 138 | columns = [desc[0] for desc in conn.description] 139 | 140 | return [dict(zip(columns, row)) for row in result] 141 | 142 | def search_emails_by_content( 143 | query: str, 144 | config_name: Optional[str] = None, 145 | limit: int = 10 146 | ) -> List[Dict[str, Any]]: 147 | """ 148 | Basic text search in email subject and body. 149 | This will be enhanced with vector search later. 150 | """ 151 | with duckdb.connect(str(DB_FILE)) as conn: 152 | sql_query = """ 153 | SELECT * FROM emails 154 | WHERE (subject ILIKE ? OR body ILIKE ?) 155 | """ 156 | params = [f'%{query}%', f'%{query}%'] 157 | 158 | if config_name: 159 | sql_query += " AND config_name = ?" 160 | params.append(config_name) 161 | 162 | sql_query += " ORDER BY date DESC LIMIT ?" 163 | params.append(limit) 164 | 165 | result = conn.execute(sql_query, params).fetchall() 166 | columns = [desc[0] for desc in conn.description] 167 | 168 | return [dict(zip(columns, row)) for row in result] 169 | 170 | def get_email_count(config_name: Optional[str] = None) -> int: 171 | """Get the total count of emails in the database.""" 172 | with duckdb.connect(str(DB_FILE)) as conn: 173 | query = "SELECT COUNT(*) FROM emails" 174 | params = [] 175 | 176 | if config_name: 177 | query += " WHERE config_name = ?" 178 | params.append(config_name) 179 | 180 | result = conn.execute(query, params).fetchone() 181 | return result[0] if result else 0 182 | 183 | # Initialize the database when the module is imported 184 | init_db() -------------------------------------------------------------------------------- /src/mcp_email_client/mailhandler.py: -------------------------------------------------------------------------------- 1 | from mcp_email_client.config import MailConfig 2 | import smtplib, imaplib 3 | import datetime 4 | from email import message_from_bytes 5 | from mcp_email_client.db import ( 6 | insert_email, 7 | get_emails, 8 | search_emails_by_content, 9 | get_emails_by_date_range, 10 | get_all_emails, 11 | get_email_count 12 | ) 13 | from mcp_email_client.semantic import ( 14 | generate_embedding, 15 | update_email_with_embedding, 16 | semantic_search, 17 | generate_embeddings_for_all 18 | ) 19 | 20 | def handleAddConfig(name: str, **kwargs): 21 | config = MailConfig(name, **kwargs) 22 | # Implementation of adding email configuration using the provided name and inbound host 23 | return f"Email configuration '{config.name}' added successfully." 24 | 25 | def handleUpdateConfig(name: str, **kwargs): 26 | config = MailConfig.load_entry(name) 27 | config.update(**kwargs) 28 | return f"Email configuration '{name}' updated successfully." 29 | 30 | def handleDeleteConfig(name: str): 31 | MailConfig.delete_entry(name) 32 | return f"Email configuration '{name}' deleted successfully." 33 | 34 | def handleListConfigs(): 35 | configs = MailConfig.load_all() 36 | result = {} 37 | for config in configs: 38 | try: 39 | with open(config.config_file, 'r') as f: 40 | file_content = f.read() 41 | result[config.name] = file_content 42 | except Exception as e: 43 | result[config.name] = f"Error reading file: {str(e)}" 44 | return result 45 | 46 | def handleSendEmail(config_name: str, subject: str, body: str, to: str, cc: str = None, bcc: str = None): 47 | config = MailConfig.load_entry(config_name) 48 | if not config: 49 | return f"Email configuration '{config_name}' not found." 50 | try: 51 | if config.outbound_ssl == "SSL/TLS": 52 | server = smtplib.SMTP_SSL(config.outbound_host, config.outbound_port) 53 | else: 54 | server = smtplib.SMTP(config.outbound_host, config.outbound_port) 55 | if config.outbound_ssl == "STARTTLS": 56 | server.starttls() 57 | server.login(config.outbound_user, config.outbound_password) 58 | message = f"Subject: {subject}\n\n{body}" 59 | server.sendmail(config.outbound_user, to, message) 60 | server.quit() 61 | 62 | # Store the sent email in the database 63 | email_id = insert_email( 64 | config_name=config_name, 65 | subject=subject, 66 | body=body, 67 | sender=config.outbound_user, 68 | recipients=to, 69 | cc=cc, 70 | bcc=bcc, 71 | raw_content=message 72 | ) 73 | 74 | # Generate and store embedding for the email 75 | if email_id and subject and body: 76 | combined_text = f"{subject} {body}" 77 | update_email_with_embedding(email_id, combined_text) 78 | 79 | return f"Email sent successfully." 80 | except Exception as e: 81 | return f"Failed to send email: {str(e)}" 82 | 83 | def handleLoadHundredLatestEmails(config_name: str): 84 | config = MailConfig.load_entry(config_name) 85 | if not config: 86 | return f"Email configuration '{config_name}' not found." 87 | 88 | try: 89 | # More comprehensive SSL handling logic 90 | ssl_value = config.inbound_ssl.lower() if config.inbound_ssl else "" 91 | 92 | # Check for any variation of SSL or TLS 93 | if "ssl" in ssl_value or "tls" in ssl_value: 94 | # Don't use STARTTLS mode for direct SSL/TLS connection 95 | if "starttls" not in ssl_value: 96 | mail = imaplib.IMAP4_SSL(config.inbound_host, config.inbound_port) 97 | else: 98 | # Use STARTTLS 99 | mail = imaplib.IMAP4(config.inbound_host, config.inbound_port) 100 | mail.starttls() 101 | else: 102 | # Plain, unencrypted connection 103 | mail = imaplib.IMAP4(config.inbound_host, config.inbound_port) 104 | 105 | mail.login(config.inbound_user, config.inbound_password) 106 | mail.select('inbox') 107 | _, data = mail.search(None, 'ALL') 108 | 109 | # Check if we got any email IDs 110 | if not data or not data[0]: 111 | return "No emails found in the inbox." 112 | 113 | latest_ids = data[0].split()[-100:] # Get only the 5 latest emails 114 | emails = [] 115 | 116 | for email_id in latest_ids: 117 | _, msg_data = mail.fetch(email_id, '(RFC822)') 118 | if not msg_data or not msg_data[0]: 119 | continue 120 | 121 | raw_email = msg_data[0][1] 122 | emails.append(raw_email.decode('utf-8')) 123 | 124 | # Parse the email message 125 | msg = message_from_bytes(raw_email) 126 | 127 | # Extract email components 128 | subject = msg.get('Subject', '') 129 | from_addr = msg.get('From', '') 130 | to_addrs = msg.get('To', '') 131 | cc_addrs = msg.get('Cc', '') 132 | 133 | # Get the email body 134 | body = "" 135 | if msg.is_multipart(): 136 | for part in msg.walk(): 137 | if part.get_content_type() == "text/plain": 138 | try: 139 | body = part.get_payload(decode=True).decode() 140 | break 141 | except: 142 | body = "Unable to decode email body" 143 | else: 144 | try: 145 | body = msg.get_payload(decode=True).decode() 146 | except: 147 | body = "Unable to decode email body" 148 | 149 | # Skip database operations for now since they're causing errors 150 | # Instead, return the parsed email information directly 151 | emails[-1] = { 152 | 'subject': subject, 153 | 'sender': from_addr, 154 | 'recipients': to_addrs, 155 | 'cc': cc_addrs, 156 | 'body': body[:500] + ('...' if len(body) > 500 else '') # Truncate long bodies 157 | } 158 | email_id = insert_email( 159 | config_name=config_name, 160 | subject=subject, 161 | body=body, 162 | sender=from_addr, 163 | recipients=to_addrs, 164 | cc=cc_addrs, 165 | bcc=None, # BCC is not available in received emails 166 | date=datetime.datetime.now(), 167 | raw_content=raw_email.decode('utf-8') 168 | ) 169 | # Generate and store embedding for the email 170 | if email_id and subject and body: 171 | combined_text = f"{subject} {body}" 172 | update_email_with_embedding(email_id, combined_text) 173 | 174 | mail.logout() 175 | 176 | if not emails: 177 | return "No emails found in the inbox." 178 | 179 | return emails 180 | except Exception as e: 181 | return f"Failed to load emails: {str(e)}" 182 | 183 | def handleLoadEmailsByDateRange( 184 | config_name: str, 185 | start_date: str, 186 | end_date: str = None, 187 | limit: int = 100 188 | ): 189 | """Load emails within a specific date range.""" 190 | try: 191 | # Parse date strings to datetime objects 192 | start_dt = datetime.datetime.fromisoformat(start_date) 193 | end_dt = datetime.datetime.fromisoformat(end_date) if end_date else datetime.datetime.now() 194 | 195 | # Get emails from the database for the specified date range 196 | emails = get_emails_by_date_range( 197 | start_date=start_dt, 198 | end_date=end_dt, 199 | config_name=config_name, 200 | limit=limit 201 | ) 202 | 203 | return emails 204 | except Exception as e: 205 | return f"Failed to load emails by date range: {str(e)}" 206 | 207 | def handleLoadAllEmails(config_name: str = None, limit: int = 1000): 208 | """Load all emails from the database with optional filtering by config name. 209 | If there are no emails in the database, load emails from the server.""" 210 | try: 211 | # Check if there are any emails in the database 212 | count = get_email_count(config_name=config_name) 213 | 214 | # If there are emails, get them from the database as before 215 | if count > 0: 216 | emails = get_all_emails(config_name=config_name, limit=limit) 217 | return emails 218 | 219 | # If no emails in database and no config specified, can't load from server 220 | if not config_name: 221 | return "No emails in database and no config name specified to load from server." 222 | 223 | # Load from server if no emails in database 224 | config = MailConfig.load_entry(config_name) 225 | if not config: 226 | return f"Email configuration '{config_name}' not found." 227 | 228 | try: 229 | # SSL handling logic from handleLoadHundredLatestEmails 230 | ssl_value = config.inbound_ssl.lower() if config.inbound_ssl else "" 231 | 232 | if "ssl" in ssl_value or "tls" in ssl_value: 233 | if "starttls" not in ssl_value: 234 | mail = imaplib.IMAP4_SSL(config.inbound_host, config.inbound_port) 235 | else: 236 | mail = imaplib.IMAP4(config.inbound_host, config.inbound_port) 237 | mail.starttls() 238 | else: 239 | mail = imaplib.IMAP4(config.inbound_host, config.inbound_port) 240 | 241 | mail.login(config.inbound_user, config.inbound_password) 242 | mail.select('inbox') 243 | _, data = mail.search(None, 'ALL') 244 | 245 | if not data or not data[0]: 246 | return "No emails found in the inbox." 247 | 248 | latest_ids = data[0].split() 249 | if len(latest_ids) > limit: 250 | latest_ids = latest_ids[-limit:] # Get only the latest 'limit' emails 251 | 252 | emails = [] 253 | 254 | for email_id in latest_ids: 255 | _, msg_data = mail.fetch(email_id, '(RFC822)') 256 | if not msg_data or not msg_data[0]: 257 | continue 258 | 259 | raw_email = msg_data[0][1] 260 | 261 | # Parse the email message 262 | msg = message_from_bytes(raw_email) 263 | 264 | # Extract email components 265 | subject = msg.get('Subject', '') 266 | from_addr = msg.get('From', '') 267 | to_addrs = msg.get('To', '') 268 | cc_addrs = msg.get('Cc', '') 269 | 270 | # Get the email body 271 | body = "" 272 | if msg.is_multipart(): 273 | for part in msg.walk(): 274 | if part.get_content_type() == "text/plain": 275 | try: 276 | body = part.get_payload(decode=True).decode() 277 | break 278 | except: 279 | body = "Unable to decode email body" 280 | else: 281 | try: 282 | body = msg.get_payload(decode=True).decode() 283 | except: 284 | body = "Unable to decode email body" 285 | 286 | # Store the email in the database 287 | email_id = insert_email( 288 | config_name=config_name, 289 | subject=subject, 290 | body=body, 291 | sender=from_addr, 292 | recipients=to_addrs, 293 | cc=cc_addrs, 294 | bcc=None, # BCC is not available in received emails 295 | date=datetime.datetime.now(), 296 | raw_content=raw_email.decode('utf-8', errors='replace') 297 | ) 298 | 299 | # Add parsed email to result list 300 | emails.append({ 301 | 'id': email_id, 302 | 'subject': subject, 303 | 'sender': from_addr, 304 | 'recipients': to_addrs, 305 | 'cc': cc_addrs, 306 | 'body': body[:500] + ('...' if len(body) > 500 else '') # Truncate long bodies 307 | }) 308 | 309 | mail.logout() 310 | 311 | if not emails: 312 | return "No emails found in the inbox." 313 | 314 | return emails 315 | 316 | except Exception as e: 317 | return f"Failed to load emails from server: {str(e)}" 318 | 319 | except Exception as e: 320 | return f"Failed to load all emails: {str(e)}" 321 | 322 | def handleGetEmailCount(config_name: str = None): 323 | """Get the total count of emails in the database.""" 324 | try: 325 | count = get_email_count(config_name=config_name) 326 | return {"email_count": count} 327 | except Exception as e: 328 | return f"Failed to get email count: {str(e)}" 329 | 330 | def handleSearchEmails(config_name: str = None, query: str = None, limit: int = 10): 331 | """Search emails using text search on subject and body""" 332 | try: 333 | results = search_emails_by_content(query, config_name, limit) 334 | return results 335 | except Exception as e: 336 | return f"Failed to search emails: {str(e)}" 337 | 338 | def handleSemanticSearchEmails( 339 | query: str, 340 | config_name: str = None, 341 | similarity_threshold: float = 0.6, 342 | limit: int = 10 343 | ): 344 | """Search emails using semantic similarity""" 345 | try: 346 | results = semantic_search( 347 | query=query, 348 | config_name=config_name, 349 | similarity_threshold=similarity_threshold, 350 | limit=limit 351 | ) 352 | return results 353 | except Exception as e: 354 | return f"Failed to perform semantic search: {str(e)}" 355 | 356 | def handleGenerateEmbeddings(batch_size: int = 100): 357 | """Generate embeddings for all emails that don't have them yet""" 358 | try: 359 | stats = generate_embeddings_for_all(batch_size=batch_size) 360 | return stats 361 | except Exception as e: 362 | return f"Failed to generate embeddings: {str(e)}" 363 | -------------------------------------------------------------------------------- /src/mcp_email_client/semantic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sentence_transformers import SentenceTransformer 3 | from typing import List, Dict, Any, Union, Optional 4 | import duckdb 5 | from pathlib import Path 6 | 7 | # Path to the database file 8 | DB_FILE = Path("emails.duckdb") 9 | 10 | # Initialize the sentence transformer model for creating embeddings 11 | # Using a smaller and faster model that's still effective for semantic search 12 | model = SentenceTransformer('paraphrase-MiniLM-L6-v2') 13 | 14 | def generate_embedding(text: str) -> bytes: 15 | """Generate an embedding vector for the given text.""" 16 | # Generate embeddings 17 | embedding = model.encode(text, show_progress_bar=False) 18 | # Convert to bytes for storage in DuckDB 19 | return embedding.tobytes() 20 | 21 | def text_to_embedding(text: str) -> np.ndarray: 22 | """Convert text to embedding vector.""" 23 | return model.encode(text, show_progress_bar=False) 24 | 25 | def bytes_to_embedding(embedding_bytes: bytes) -> np.ndarray: 26 | """Convert bytes back to numpy array for similarity calculations.""" 27 | if embedding_bytes is None: 28 | return None 29 | return np.frombuffer(embedding_bytes, dtype=np.float32) 30 | 31 | def cosine_similarity(vec1: np.ndarray, vec2: np.ndarray) -> float: 32 | """Calculate cosine similarity between two vectors.""" 33 | if vec1 is None or vec2 is None: 34 | return 0.0 35 | vec1_norm = vec1 / np.linalg.norm(vec1) 36 | vec2_norm = vec2 / np.linalg.norm(vec2) 37 | return np.dot(vec1_norm, vec2_norm) 38 | 39 | def update_email_with_embedding(email_id: int, combined_text: str) -> None: 40 | """Update an existing email with its embedding vector.""" 41 | embedding = generate_embedding(combined_text) 42 | with duckdb.connect(str(DB_FILE)) as conn: 43 | conn.execute( 44 | "UPDATE emails SET embedding = ? WHERE id = ?", 45 | (embedding, email_id) 46 | ) 47 | 48 | def semantic_search( 49 | query: str, 50 | config_name: Optional[str] = None, 51 | similarity_threshold: float = 0.3, 52 | limit: int = 10 53 | ) -> List[Dict[str, Any]]: 54 | """ 55 | Search emails using semantic similarity with embeddings. 56 | 57 | Args: 58 | query: The search query 59 | config_name: Optional email configuration name for filtering 60 | similarity_threshold: Minimum similarity score (0-1) to include in results 61 | limit: Maximum number of results to return 62 | 63 | Returns: 64 | List of email dictionaries with similarity scores, sorted by score 65 | """ 66 | # Generate embedding for the query 67 | query_embedding = text_to_embedding(query) 68 | 69 | # Get all emails (could be optimized for large datasets) 70 | with duckdb.connect(str(DB_FILE)) as conn: 71 | sql_query = "SELECT * FROM emails WHERE embedding IS NOT NULL" 72 | params = [] 73 | 74 | if config_name: 75 | sql_query += " AND config_name = ?" 76 | params.append(config_name) 77 | 78 | result = conn.execute(sql_query, params).fetchall() 79 | columns = [desc[0] for desc in conn.description] 80 | 81 | # Convert result to list of dictionaries 82 | emails = [dict(zip(columns, row)) for row in result] 83 | 84 | # Calculate similarity for each email 85 | scored_emails = [] 86 | for email in emails: 87 | # Convert stored embedding bytes back to numpy array 88 | email_embedding = bytes_to_embedding(email.get('embedding')) 89 | if email_embedding is not None: 90 | # Calculate similarity score 91 | similarity = cosine_similarity(query_embedding, email_embedding) 92 | #if similarity >= similarity_threshold: 93 | # Add similarity score to email dictionary 94 | if similarity >= similarity_threshold: 95 | email_with_score = email.copy() 96 | del email_with_score['body'] 97 | del email_with_score['embedding'] 98 | del email_with_score['raw_content'] # Remove embedding from output 99 | email_with_score['similarity_score'] = float(similarity) 100 | scored_emails.append(email_with_score) 101 | 102 | # Sort by similarity score (highest first) and limit results 103 | sorted_emails = sorted(scored_emails, key=lambda x: x.get('similarity_score', 0), reverse=True) 104 | return sorted_emails[:limit] 105 | 106 | def generate_embeddings_for_all(batch_size: int = 100) -> Dict[str, Any]: 107 | """ 108 | Generate embeddings for all emails that don't have them yet. 109 | Returns statistics about the process. 110 | """ 111 | with duckdb.connect(str(DB_FILE)) as conn: 112 | # Get count of emails without embeddings 113 | missing_count = conn.execute( 114 | "SELECT COUNT(*) FROM emails WHERE embedding IS NULL" 115 | ).fetchone()[0] 116 | 117 | # Get emails without embeddings 118 | result = conn.execute( 119 | "SELECT id, subject, body FROM emails WHERE embedding IS NULL" 120 | ).fetchall() 121 | 122 | processed = 0 123 | 124 | # Process in batches 125 | for row in result: 126 | email_id, subject, body = row 127 | # Combine subject and body for better semantic context 128 | combined_text = f"{subject} {body}" if subject and body else subject or body or "" 129 | 130 | # Generate and store embedding 131 | if combined_text.strip(): 132 | embedding = generate_embedding(combined_text) 133 | conn.execute( 134 | "UPDATE emails SET embedding = ? WHERE id = ?", 135 | (embedding, email_id) 136 | ) 137 | processed += 1 138 | 139 | # Log progress for large datasets 140 | if processed % batch_size == 0: 141 | print(f"Processed {processed}/{missing_count} emails") 142 | 143 | # Get total count of emails with embeddings 144 | total_with_embeddings = conn.execute( 145 | "SELECT COUNT(*) FROM emails WHERE embedding IS NOT NULL" 146 | ).fetchone()[0] 147 | 148 | return { 149 | "processed_in_this_run": processed, 150 | "total_with_embeddings": total_with_embeddings, 151 | "total_emails": processed + total_with_embeddings 152 | } -------------------------------------------------------------------------------- /src/mcp_email_client/server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | from pathlib import Path 4 | from mcp.server import Server 5 | from mcp.server.stdio import stdio_server 6 | from mcp.types import ( 7 | TextContent, 8 | Tool, 9 | ) 10 | from mcp_email_client.mailhandler import ( 11 | handleAddConfig, 12 | handleUpdateConfig, 13 | handleDeleteConfig, 14 | handleListConfigs, 15 | handleSendEmail, 16 | handleLoadHundredLatestEmails, 17 | handleSearchEmails, 18 | handleLoadEmailsByDateRange, 19 | handleLoadAllEmails, 20 | handleGetEmailCount, 21 | handleSemanticSearchEmails, 22 | handleGenerateEmbeddings 23 | ) 24 | 25 | async def serve(repository: Path | None) -> None: 26 | logger = logging.getLogger(__name__) 27 | server = Server("EmailClient") 28 | 29 | @server.list_tools() 30 | async def list_tools() -> list[Tool]: 31 | return [ 32 | Tool( 33 | name="list_email_configs", 34 | description="List all email configurations", 35 | inputSchema={ 36 | "type": "object", 37 | "properties": { 38 | "command": {"type": "string"}, 39 | }, 40 | "required": [""], 41 | } 42 | ), 43 | Tool( 44 | name="add_email_config", 45 | description="Add a new email configuration", 46 | inputSchema={ 47 | "type": "object", 48 | "properties": { 49 | "name": {"type": "string"}, 50 | "inbound_user": {"type": "string"}, 51 | "inbound_password": {"type": "string"}, 52 | "inbound_host": {"type": "string"}, 53 | "inbound_port": {"type": "integer"}, 54 | "inbound_ssl": {"type": "string"}, 55 | "is_outbound_equal": {"type": "boolean"}, 56 | "outbound_user": {"type": "string"}, 57 | "outbound_password": {"type": "string"}, 58 | "outbound_host": {"type": "string"}, 59 | "outbound_port": {"type": "integer"}, 60 | "outbound_ssl": {"type": "string"}, 61 | }, 62 | "required": ["name", "inbound_user", "inbound_password", "inbound_host", "inbound_port", "inbound_ssl", "is_outbound_equal"], 63 | } 64 | ), 65 | Tool( 66 | name="update_email_config", 67 | description="Update email configuration", 68 | inputSchema={ 69 | "type": "object", 70 | "properties": { 71 | "name": {"type": "string"}, 72 | "inbound_user": {"type": "string"}, 73 | "inbound_password": {"type": "string"}, 74 | "inbound_host": {"type": "string"}, 75 | "inbound_port": {"type": "integer"}, 76 | "inbound_ssl": {"type": "string"}, 77 | "is_outbound_equal": {"type": "boolean"}, 78 | "outbound_user": {"type": "string"}, 79 | "outbound_password": {"type": "string"}, 80 | "outbound_host": {"type": "string"}, 81 | "outbound_port": {"type": "integer"}, 82 | "outbound_ssl": {"type": "string"}, 83 | }, 84 | "required": ["name", "inbound_user", "inbound_password", "inbound_host", "inbound_port", "inbound_ssl", "is_outbound_equal"], 85 | } 86 | ), 87 | Tool( 88 | name="delete_email_config", 89 | description="Delete email configuration", 90 | inputSchema={ 91 | "type": "object", 92 | "properties": { 93 | "name": {"type": "string"}, 94 | }, 95 | "required": ["name"], 96 | } 97 | ), 98 | Tool( 99 | name="send_email", 100 | description="Send an email", 101 | inputSchema={ 102 | "type": "object", 103 | "properties": { 104 | "name": {"type": "string"}, 105 | "subject": {"type": "string"}, 106 | "body": {"type": "string"}, 107 | "to": {"type": "string"}, 108 | "cc": {"type": "string"}, 109 | "bcc": {"type": "string"}, 110 | }, 111 | "required": ["name", "subject", "body", "to"], 112 | } 113 | ), 114 | Tool( 115 | name="read_email", 116 | description="Read latest 5 unread emails", 117 | inputSchema={ 118 | "type": "object", 119 | "properties": { 120 | "name": {"type": "string"}, 121 | }, 122 | "required": ["name"], 123 | } 124 | ), 125 | Tool( 126 | name="search_emails", 127 | description="Search emails by content", 128 | inputSchema={ 129 | "type": "object", 130 | "properties": { 131 | "name": {"type": "string", "description": "Email configuration name (optional)"}, 132 | "query": {"type": "string", "description": "Search query text"}, 133 | "limit": {"type": "integer", "description": "Maximum number of results (default: 10)"}, 134 | }, 135 | "required": ["query"], 136 | } 137 | ), 138 | Tool( 139 | name="get_emails_by_date", 140 | description="Get emails within a date range", 141 | inputSchema={ 142 | "type": "object", 143 | "properties": { 144 | "name": {"type": "string", "description": "Email configuration name"}, 145 | "start_date": {"type": "string", "description": "Start date in ISO format (YYYY-MM-DD)"}, 146 | "end_date": {"type": "string", "description": "End date in ISO format (YYYY-MM-DD), defaults to today if not provided"}, 147 | "limit": {"type": "integer", "description": "Maximum number of results (default: 100)"}, 148 | }, 149 | "required": ["name", "start_date"], 150 | } 151 | ), 152 | Tool( 153 | name="get_all_emails", 154 | description="Get all emails from the database", 155 | inputSchema={ 156 | "type": "object", 157 | "properties": { 158 | "name": {"type": "string", "description": "Email configuration name (optional)"}, 159 | "limit": {"type": "integer", "description": "Maximum number of results (default: 1000)"}, 160 | }, 161 | "required": [], 162 | } 163 | ), 164 | Tool( 165 | name="get_email_count", 166 | description="Get the total count of emails in the database", 167 | inputSchema={ 168 | "type": "object", 169 | "properties": { 170 | "name": {"type": "string", "description": "Email configuration name (optional)"}, 171 | }, 172 | "required": [], 173 | } 174 | ), 175 | Tool( 176 | name="semantic_search_emails", 177 | description="Search emails using semantic similarity", 178 | inputSchema={ 179 | "type": "object", 180 | "properties": { 181 | "query": {"type": "string", "description": "Search query text"}, 182 | "name": {"type": "string", "description": "Email configuration name (optional)"}, 183 | "similarity_threshold": {"type": "number", "description": "Minimum similarity score (0-1) to include in results (default: 0.6)"}, 184 | "limit": {"type": "integer", "description": "Maximum number of results (default: 10)"}, 185 | }, 186 | "required": ["query"], 187 | } 188 | ), 189 | Tool( 190 | name="generate_embeddings", 191 | description="Generate embeddings for all emails that don't have them yet", 192 | inputSchema={ 193 | "type": "object", 194 | "properties": { 195 | "batch_size": {"type": "integer", "description": "Number of emails to process in each batch (default: 100)"}, 196 | }, 197 | "required": [], 198 | } 199 | ), 200 | ] 201 | 202 | @server.call_tool() 203 | async def call_tool(name: str, arguments: dict) -> list[TextContent]: 204 | if name == "list_email_configs": 205 | list_config = handleListConfigs() 206 | return [TextContent(type="text",text=f'Email configs:{list_config}')] 207 | elif name == "add_email_config": 208 | config_name = arguments.get("name") 209 | del arguments["name"] 210 | add_config = handleAddConfig(config_name, **arguments) 211 | return [TextContent(type="text",text=f'Email config added:{add_config}')] 212 | elif name == "update_email_config": 213 | config_name = arguments.get("name") 214 | del arguments["name"] 215 | update_config = handleUpdateConfig(config_name, **arguments) 216 | return [TextContent(type="text",text=f'Email config updated:{update_config}')] 217 | elif name == "delete_email_config": 218 | config_name = arguments.get("name") 219 | delete_config = handleDeleteConfig(config_name) 220 | return [TextContent(type="text",text=f'Email config deleted:{delete_config}')] 221 | elif name == "send_email": 222 | config_name = arguments.get("name") 223 | send_email = handleSendEmail( 224 | config_name=config_name, 225 | subject=arguments.get("subject"), 226 | body=arguments.get("body"), 227 | to=arguments.get("to"), 228 | cc=arguments.get("cc"), 229 | bcc=arguments.get("bcc") 230 | ) 231 | return [TextContent(type="text",text=f'Email sent:{send_email}')] 232 | elif name == "read_email": 233 | config_name = arguments.get("name") 234 | read_emails = handleLoadHundredLatestEmails(config_name) 235 | return [TextContent(type="text",text=f'Email received:{read_emails}')] 236 | elif name == "search_emails": 237 | config_name = arguments.get("name") # Optional 238 | query = arguments.get("query") 239 | limit = arguments.get("limit", 10) 240 | search_results = handleSearchEmails(config_name, query, limit) 241 | return [TextContent(type="text",text=f'Search results:{search_results}')] 242 | elif name == "get_emails_by_date": 243 | config_name = arguments.get("name") 244 | start_date = arguments.get("start_date") 245 | end_date = arguments.get("end_date") 246 | limit = arguments.get("limit", 100) 247 | results = handleLoadEmailsByDateRange(config_name, start_date, end_date, limit) 248 | return [TextContent(type="text",text=f'Emails by date range:{results}')] 249 | elif name == "get_all_emails": 250 | config_name = arguments.get("name") 251 | limit = arguments.get("limit", 1000) 252 | results = handleLoadAllEmails(config_name, limit) 253 | return [TextContent(type="text",text=f'All emails:{results}')] 254 | elif name == "get_email_count": 255 | config_name = arguments.get("name") 256 | count = handleGetEmailCount(config_name) 257 | return [TextContent(type="text",text=f'Email count:{count}')] 258 | elif name == "semantic_search_emails": 259 | query = arguments.get("query") 260 | config_name = arguments.get("name") 261 | similarity_threshold = arguments.get("similarity_threshold", 0.6) 262 | limit = arguments.get("limit", 10) 263 | search_results = handleSemanticSearchEmails( 264 | query=query, 265 | config_name=config_name, 266 | similarity_threshold=similarity_threshold, 267 | limit=limit 268 | ) 269 | return [TextContent(type="text",text=f'Semantic search results:{search_results}')] 270 | elif name == "generate_embeddings": 271 | batch_size = arguments.get("batch_size", 100) 272 | stats = handleGenerateEmbeddings(batch_size=batch_size) 273 | return [TextContent(type="text",text=f'Embedding generation stats:{stats}')] 274 | else: 275 | raise ValueError(f"Unknown tool: {name}") 276 | 277 | options = server.create_initialization_options() 278 | async with stdio_server() as (read_stream, write_stream): 279 | await server.run(read_stream, write_stream, options, raise_exceptions=True) 280 | 281 | 282 | def main() -> None: 283 | logging.basicConfig(level=logging.INFO) 284 | async def _run(): 285 | server = await serve() 286 | options = server.create_initialization_options() 287 | async with stdio_server() as (read_stream, write_stream): 288 | await server.run(read_stream, write_stream, options, raise_exceptions=True) 289 | asyncio.run(_run()) -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamalan/mcp-email-client/51e431a777cbc10ec8aecd9507d28e330e1d3247/template/.gitignore -------------------------------------------------------------------------------- /tests/test_db.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | import datetime 5 | from pathlib import Path 6 | from unittest.mock import patch, MagicMock 7 | 8 | # Add parent directory to path to import modules 9 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 10 | 11 | import mcp_email_client.db as db 12 | 13 | class TestDatabaseOperations(unittest.TestCase): 14 | """Test database operations in the db module.""" 15 | 16 | def setUp(self): 17 | """Set up test environment.""" 18 | # Use a test database file 19 | self.original_db_file = db.DB_FILE 20 | db.DB_FILE = Path("test_emails.duckdb") 21 | db.init_db() 22 | 23 | def tearDown(self): 24 | """Clean up after tests.""" 25 | # Remove test database 26 | if Path("test_emails.duckdb").exists(): 27 | os.remove("test_emails.duckdb") 28 | # Restore original DB file 29 | db.DB_FILE = self.original_db_file 30 | 31 | def test_insert_and_get_email(self): 32 | """Test inserting and retrieving an email.""" 33 | # Insert test email 34 | email_id = db.insert_email( 35 | config_name="test_config", 36 | subject="Test Subject", 37 | body="Test Body", 38 | sender="sender@example.com", 39 | recipients="recipient@example.com", 40 | cc="cc@example.com", 41 | bcc="bcc@example.com" 42 | ) 43 | 44 | # Verify email was inserted and has an ID 45 | self.assertIsNotNone(email_id) 46 | 47 | # Retrieve the email 48 | emails = db.get_emails(config_name="test_config") 49 | 50 | # Verify email was retrieved correctly 51 | self.assertEqual(len(emails), 1) 52 | self.assertEqual(emails[0]['subject'], "Test Subject") 53 | self.assertEqual(emails[0]['body'], "Test Body") 54 | self.assertEqual(emails[0]['sender'], "sender@example.com") 55 | self.assertEqual(emails[0]['recipients'], "recipient@example.com") 56 | self.assertEqual(emails[0]['cc'], "cc@example.com") 57 | self.assertEqual(emails[0]['bcc'], "bcc@example.com") 58 | 59 | def test_get_emails_by_date_range(self): 60 | """Test retrieving emails within a date range.""" 61 | # Insert test emails with different dates 62 | past_date = datetime.datetime.now() - datetime.timedelta(days=10) 63 | recent_date = datetime.datetime.now() - datetime.timedelta(days=2) 64 | 65 | # Email from 10 days ago 66 | db.insert_email( 67 | config_name="test_config", 68 | subject="Old Email", 69 | body="Old Body", 70 | sender="old@example.com", 71 | recipients="recipient@example.com", 72 | date=past_date 73 | ) 74 | 75 | # Email from 2 days ago 76 | db.insert_email( 77 | config_name="test_config", 78 | subject="Recent Email", 79 | body="Recent Body", 80 | sender="recent@example.com", 81 | recipients="recipient@example.com", 82 | date=recent_date 83 | ) 84 | 85 | # Get emails from the last 5 days 86 | start_date = datetime.datetime.now() - datetime.timedelta(days=5) 87 | emails = db.get_emails_by_date_range( 88 | start_date=start_date, 89 | config_name="test_config" 90 | ) 91 | 92 | # Should only get the recent email 93 | self.assertEqual(len(emails), 1) 94 | self.assertEqual(emails[0]['subject'], "Recent Email") 95 | 96 | # Get all emails 97 | all_emails = db.get_emails_by_date_range( 98 | start_date=past_date - datetime.timedelta(days=1), 99 | config_name="test_config" 100 | ) 101 | 102 | # Should get both emails 103 | self.assertEqual(len(all_emails), 2) 104 | 105 | def test_search_emails_by_content(self): 106 | """Test searching emails by content.""" 107 | # Insert test emails with different content 108 | db.insert_email( 109 | config_name="test_config", 110 | subject="Meeting Agenda", 111 | body="We will discuss the new project timeline.", 112 | sender="manager@example.com", 113 | recipients="team@example.com" 114 | ) 115 | 116 | db.insert_email( 117 | config_name="test_config", 118 | subject="Project Update", 119 | body="The timeline has been extended by two weeks.", 120 | sender="manager@example.com", 121 | recipients="team@example.com" 122 | ) 123 | 124 | # Search for "timeline" 125 | results = db.search_emails_by_content("timeline", "test_config") 126 | 127 | # Should find both emails 128 | self.assertEqual(len(results), 2) 129 | 130 | # Search for "project" 131 | results = db.search_emails_by_content("project", "test_config") 132 | 133 | # Should find both emails 134 | self.assertEqual(len(results), 2) 135 | 136 | # Search for "meeting" 137 | results = db.search_emails_by_content("meeting", "test_config") 138 | 139 | # Should find only the meeting email 140 | self.assertEqual(len(results), 1) 141 | self.assertEqual(results[0]['subject'], "Meeting Agenda") 142 | 143 | # Search for "extended" 144 | results = db.search_emails_by_content("extended", "test_config") 145 | 146 | # Should find only the update email 147 | self.assertEqual(len(results), 1) 148 | self.assertEqual(results[0]['subject'], "Project Update") 149 | 150 | def test_email_count(self): 151 | """Test counting emails in the database.""" 152 | # Insert test emails 153 | db.insert_email( 154 | config_name="test_config1", 155 | subject="Email 1", 156 | body="Body 1", 157 | sender="sender1@example.com", 158 | recipients="recipient@example.com" 159 | ) 160 | 161 | db.insert_email( 162 | config_name="test_config1", 163 | subject="Email 2", 164 | body="Body 2", 165 | sender="sender2@example.com", 166 | recipients="recipient@example.com" 167 | ) 168 | 169 | db.insert_email( 170 | config_name="test_config2", 171 | subject="Email 3", 172 | body="Body 3", 173 | sender="sender3@example.com", 174 | recipients="recipient@example.com" 175 | ) 176 | 177 | # Count all emails 178 | count = db.get_email_count() 179 | self.assertEqual(count, 3) 180 | 181 | # Count emails for test_config1 182 | count = db.get_email_count(config_name="test_config1") 183 | self.assertEqual(count, 2) 184 | 185 | # Count emails for test_config2 186 | count = db.get_email_count(config_name="test_config2") 187 | self.assertEqual(count, 1) 188 | 189 | if __name__ == "__main__": 190 | unittest.main() -------------------------------------------------------------------------------- /tests/test_mail.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | from unittest.mock import patch, MagicMock 5 | from pathlib import Path 6 | import datetime 7 | 8 | # Add parent directory to path to import modules 9 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 10 | 11 | # Import with mocks to avoid actual SMTP/IMAP connections 12 | with patch('smtplib.SMTP_SSL'), patch('smtplib.SMTP'), patch('imaplib.IMAP4_SSL'), patch('imaplib.IMAP4'): 13 | from mail import ( 14 | handleSendEmail, 15 | handleLoadFiveLatestEmails, 16 | handleLoadEmailsByDateRange, 17 | handleLoadAllEmails, 18 | handleSearchEmails, 19 | handleSemanticSearchEmails 20 | ) 21 | from config import MailConfig 22 | 23 | class TestMailFunctions(unittest.TestCase): 24 | """Test mail module functions.""" 25 | 26 | def setUp(self): 27 | """Set up test environment.""" 28 | # Mock the database functions 29 | self.patcher1 = patch('mail.insert_email') 30 | self.mock_insert_email = self.patcher1.start() 31 | self.mock_insert_email.return_value = 1 # Return a fake email ID 32 | 33 | self.patcher2 = patch('mail.get_emails') 34 | self.mock_get_emails = self.patcher2.start() 35 | 36 | self.patcher3 = patch('mail.search_emails_by_content') 37 | self.mock_search_emails = self.patcher3.start() 38 | 39 | self.patcher4 = patch('mail.get_emails_by_date_range') 40 | self.mock_get_emails_by_date = self.patcher4.start() 41 | 42 | self.patcher5 = patch('mail.get_all_emails') 43 | self.mock_get_all_emails = self.patcher5.start() 44 | 45 | self.patcher6 = patch('mail.semantic_search') 46 | self.mock_semantic_search = self.patcher6.start() 47 | 48 | self.patcher7 = patch('mail.update_email_with_embedding') 49 | self.mock_update_with_embedding = self.patcher7.start() 50 | 51 | # Mock the MailConfig class 52 | self.patcher_config = patch('mail.MailConfig') 53 | self.mock_config_class = self.patcher_config.start() 54 | 55 | # Create a mock config 56 | self.mock_config = MagicMock() 57 | self.mock_config.outbound_ssl = "SSL/TLS" 58 | self.mock_config.outbound_host = "smtp.example.com" 59 | self.mock_config.outbound_port = 465 60 | self.mock_config.outbound_user = "user@example.com" 61 | self.mock_config.outbound_password = "password" 62 | self.mock_config.inbound_ssl = "SSL/TLS" 63 | self.mock_config.inbound_host = "imap.example.com" 64 | self.mock_config.inbound_port = 993 65 | self.mock_config.inbound_user = "user@example.com" 66 | self.mock_config.inbound_password = "password" 67 | 68 | # Set up the load_entry method to return our mock config 69 | self.mock_config_class.load_entry.return_value = self.mock_config 70 | 71 | def tearDown(self): 72 | """Clean up after tests.""" 73 | self.patcher1.stop() 74 | self.patcher2.stop() 75 | self.patcher3.stop() 76 | self.patcher4.stop() 77 | self.patcher5.stop() 78 | self.patcher6.stop() 79 | self.patcher7.stop() 80 | self.patcher_config.stop() 81 | 82 | def test_handle_send_email(self): 83 | """Test sending an email.""" 84 | # Mock the SMTP server 85 | with patch('smtplib.SMTP_SSL') as mock_smtp: 86 | mock_server = MagicMock() 87 | mock_smtp.return_value = mock_server 88 | 89 | # Call the function to send an email 90 | result = handleSendEmail( 91 | config_name="test_config", 92 | subject="Test Subject", 93 | body="Test Body", 94 | to="recipient@example.com", 95 | cc="cc@example.com", 96 | bcc="bcc@example.com" 97 | ) 98 | 99 | # Verify SMTP methods were called correctly 100 | mock_server.login.assert_called_once_with("user@example.com", "password") 101 | mock_server.sendmail.assert_called_once() 102 | mock_server.quit.assert_called_once() 103 | 104 | # Verify email was inserted into database 105 | self.mock_insert_email.assert_called_once_with( 106 | config_name="test_config", 107 | subject="Test Subject", 108 | body="Test Body", 109 | sender="user@example.com", 110 | recipients="recipient@example.com", 111 | cc="cc@example.com", 112 | bcc="bcc@example.com", 113 | raw_content=mock_server.sendmail.call_args[0][2] 114 | ) 115 | 116 | # Verify embedding was generated and stored 117 | self.mock_update_with_embedding.assert_called_once_with(1, "Test Subject Test Body") 118 | 119 | # Verify function returned success message 120 | self.assertEqual(result, "Email sent successfully.") 121 | 122 | def test_handle_search_emails(self): 123 | """Test searching emails by content.""" 124 | # Setup mock search results 125 | mock_results = [ 126 | {'id': 1, 'subject': 'Email 1', 'body': 'Body 1'}, 127 | {'id': 2, 'subject': 'Email 2', 'body': 'Body 2'} 128 | ] 129 | self.mock_search_emails.return_value = mock_results 130 | 131 | # Call the search function 132 | result = handleSearchEmails( 133 | config_name="test_config", 134 | query="search term", 135 | limit=10 136 | ) 137 | 138 | # Verify search function was called correctly 139 | self.mock_search_emails.assert_called_once_with("search term", "test_config", 10) 140 | 141 | # Verify function returned the search results 142 | self.assertEqual(result, mock_results) 143 | 144 | def test_handle_semantic_search_emails(self): 145 | """Test semantic search for emails.""" 146 | # Setup mock semantic search results with similarity scores 147 | mock_results = [ 148 | {'id': 1, 'subject': 'Email 1', 'body': 'Body 1', 'similarity_score': 0.95}, 149 | {'id': 2, 'subject': 'Email 2', 'body': 'Body 2', 'similarity_score': 0.75} 150 | ] 151 | self.mock_semantic_search.return_value = mock_results 152 | 153 | # Call the semantic search function 154 | result = handleSemanticSearchEmails( 155 | query="search term", 156 | config_name="test_config", 157 | similarity_threshold=0.7, 158 | limit=10 159 | ) 160 | 161 | # Verify semantic search function was called correctly 162 | self.mock_semantic_search.assert_called_once_with( 163 | query="search term", 164 | config_name="test_config", 165 | similarity_threshold=0.7, 166 | limit=10 167 | ) 168 | 169 | # Verify function returned the semantic search results 170 | self.assertEqual(result, mock_results) 171 | 172 | def test_handle_load_emails_by_date_range(self): 173 | """Test loading emails by date range.""" 174 | # Setup mock date range results 175 | test_date = datetime.datetime(2025, 4, 1) 176 | mock_results = [ 177 | {'id': 1, 'subject': 'Email 1', 'date': test_date}, 178 | {'id': 2, 'subject': 'Email 2', 'date': test_date} 179 | ] 180 | self.mock_get_emails_by_date.return_value = mock_results 181 | 182 | # Call the function to get emails by date range 183 | result = handleLoadEmailsByDateRange( 184 | config_name="test_config", 185 | start_date="2025-04-01", 186 | end_date="2025-04-15", 187 | limit=100 188 | ) 189 | 190 | # Verify the date range function was called with correct parameters 191 | self.mock_get_emails_by_date.assert_called_once() 192 | args, kwargs = self.mock_get_emails_by_date.call_args 193 | self.assertEqual(kwargs['config_name'], "test_config") 194 | self.assertEqual(kwargs['limit'], 100) 195 | 196 | # Verify function returned the correct results 197 | self.assertEqual(result, mock_results) 198 | 199 | if __name__ == "__main__": 200 | unittest.main() -------------------------------------------------------------------------------- /tests/test_mcp_server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | from unittest.mock import patch, MagicMock, AsyncMock 5 | from pathlib import Path 6 | import json 7 | import asyncio 8 | 9 | # Add parent directory to path to import modules 10 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 11 | 12 | # Import with mocks to avoid actual connections and model loading 13 | with patch('smtplib.SMTP_SSL'), patch('smtplib.SMTP'), \ 14 | patch('imaplib.IMAP4_SSL'), patch('imaplib.IMAP4'), \ 15 | patch('sentence_transformers.SentenceTransformer'): 16 | import mailclient 17 | from mailclient import serve 18 | import mail 19 | from mail import ( 20 | handleListConfigs, 21 | handleSendEmail, 22 | handleSearchEmails, 23 | handleLoadEmailsByDateRange 24 | ) 25 | from config import MailConfig 26 | 27 | class TestMCPServer(unittest.TestCase): 28 | """Test MCP server functionality.""" 29 | 30 | def setUp(self): 31 | """Set up test environment.""" 32 | # Mock all the handler functions 33 | self.patcher_list_configs = patch('mailclient.handleListConfigs') 34 | self.mock_list_configs = self.patcher_list_configs.start() 35 | self.mock_list_configs.return_value = ["test_config"] 36 | 37 | self.patcher_send_email = patch('mailclient.handleSendEmail') 38 | self.mock_send_email = self.patcher_send_email.start() 39 | self.mock_send_email.return_value = "Email sent successfully." 40 | 41 | self.patcher_search_emails = patch('mailclient.handleSearchEmails') 42 | self.mock_search_emails = self.patcher_search_emails.start() 43 | self.mock_search_emails.return_value = [ 44 | {'id': 1, 'subject': 'Test Email', 'body': 'Test Body'} 45 | ] 46 | 47 | self.patcher_load_emails_by_date = patch('mailclient.handleLoadEmailsByDateRange') 48 | self.mock_load_emails_by_date = self.patcher_load_emails_by_date.start() 49 | self.mock_load_emails_by_date.return_value = [ 50 | {'id': 1, 'subject': 'Test Email', 'date': '2025-04-15 12:00:00'} 51 | ] 52 | 53 | # Mock the Server class and its methods 54 | self.patcher_server = patch('mcp.server.Server') 55 | self.mock_server_class = self.patcher_server.start() 56 | 57 | self.mock_server = MagicMock() 58 | self.mock_server.list_tools = MagicMock() 59 | self.mock_server.list_tools.return_value = lambda f: f 60 | self.mock_server.call_tool = MagicMock() 61 | self.mock_server.call_tool.return_value = lambda f: f 62 | 63 | self.mock_server.create_initialization_options = MagicMock() 64 | self.mock_server.create_initialization_options.return_value = {} 65 | 66 | self.mock_server.run = AsyncMock() 67 | 68 | self.mock_server_class.return_value = self.mock_server 69 | 70 | def tearDown(self): 71 | """Clean up after tests.""" 72 | self.patcher_list_configs.stop() 73 | self.patcher_send_email.stop() 74 | self.patcher_search_emails.stop() 75 | self.patcher_load_emails_by_date.stop() 76 | self.patcher_server.stop() 77 | 78 | @patch('mailclient.stdio_server') 79 | async def test_server_initialization(self, mock_stdio): 80 | """Test the server initialization.""" 81 | # Setup mock for stdio_server context manager 82 | mock_cm = AsyncMock() 83 | mock_stdio.return_value = mock_cm 84 | mock_cm.__aenter__.return_value = (AsyncMock(), AsyncMock()) 85 | 86 | # Call serve function 87 | await serve(None) 88 | 89 | # Verify Server was created with correct name 90 | self.mock_server_class.assert_called_once_with("EmailClient") 91 | 92 | # Verify stdio_server was used 93 | mock_stdio.assert_called_once() 94 | 95 | # Verify server.run was called 96 | self.mock_server.run.assert_called_once() 97 | 98 | async def test_list_tools(self): 99 | """Test the list_tools handler.""" 100 | # Get the list_tools function 101 | list_tools_decorator = self.mock_server.list_tools 102 | list_tools_func = list_tools_decorator.call_args[0][0] 103 | 104 | # Call the function 105 | tools = await list_tools_func() 106 | 107 | # Verify tools list contains expected tools 108 | tool_names = [tool.name for tool in tools] 109 | self.assertIn("list_email_configs", tool_names) 110 | self.assertIn("send_email", tool_names) 111 | self.assertIn("search_emails", tool_names) 112 | self.assertIn("semantic_search_emails", tool_names) 113 | 114 | # Check the schema for a specific tool 115 | send_email_tool = next(tool for tool in tools if tool.name == "send_email") 116 | self.assertEqual(send_email_tool.description, "Send an email") 117 | self.assertIn("subject", send_email_tool.inputSchema["properties"]) 118 | self.assertIn("body", send_email_tool.inputSchema["properties"]) 119 | self.assertIn("to", send_email_tool.inputSchema["properties"]) 120 | 121 | async def test_call_tool_list_configs(self): 122 | """Test calling the list_email_configs tool.""" 123 | # Get the call_tool function 124 | call_tool_decorator = self.mock_server.call_tool 125 | call_tool_func = call_tool_decorator.call_args[0][0] 126 | 127 | # Call the function with list_email_configs 128 | result = await call_tool_func("list_email_configs", {}) 129 | 130 | # Verify the handler was called 131 | self.mock_list_configs.assert_called_once() 132 | 133 | # Verify result format 134 | self.assertEqual(len(result), 1) 135 | self.assertEqual(result[0].type, "text") 136 | self.assertIn("Email configs", result[0].text) 137 | 138 | async def test_call_tool_send_email(self): 139 | """Test calling the send_email tool.""" 140 | # Get the call_tool function 141 | call_tool_decorator = self.mock_server.call_tool 142 | call_tool_func = call_tool_decorator.call_args[0][0] 143 | 144 | # Call the function with send_email 145 | args = { 146 | "name": "test_config", 147 | "subject": "Test Subject", 148 | "body": "Test Body", 149 | "to": "recipient@example.com" 150 | } 151 | result = await call_tool_func("send_email", args) 152 | 153 | # Verify the handler was called with right args 154 | self.mock_send_email.assert_called_once() 155 | call_args, call_kwargs = self.mock_send_email.call_args 156 | self.assertEqual(call_kwargs["name"], "test_config") 157 | self.assertEqual(call_kwargs["subject"], "Test Subject") 158 | self.assertEqual(call_kwargs["body"], "Test Body") 159 | self.assertEqual(call_kwargs["to"], "recipient@example.com") 160 | 161 | # Verify result format 162 | self.assertEqual(len(result), 1) 163 | self.assertEqual(result[0].type, "text") 164 | self.assertIn("Email sent", result[0].text) 165 | 166 | async def test_call_tool_unknown(self): 167 | """Test calling an unknown tool.""" 168 | # Get the call_tool function 169 | call_tool_decorator = self.mock_server.call_tool 170 | call_tool_func = call_tool_decorator.call_args[0][0] 171 | 172 | # Call with unknown tool name 173 | with self.assertRaises(ValueError) as context: 174 | await call_tool_func("unknown_tool", {}) 175 | 176 | # Verify error message 177 | self.assertIn("Unknown tool", str(context.exception)) 178 | 179 | 180 | if __name__ == "__main__": 181 | # Run the tests with asyncio support 182 | loop = asyncio.get_event_loop() 183 | unittest.main() -------------------------------------------------------------------------------- /tests/test_semantic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import unittest 4 | import numpy as np 5 | from pathlib import Path 6 | from unittest.mock import patch, MagicMock 7 | 8 | # Add parent directory to path to import modules 9 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 10 | 11 | # Import with mock to avoid loading the actual model during tests 12 | with patch('sentence_transformers.SentenceTransformer'): 13 | import mcp_email_client.semantic as semantic 14 | import mcp_email_client.db as db 15 | 16 | class TestSemanticOperations(unittest.TestCase): 17 | """Test semantic search operations.""" 18 | 19 | def setUp(self): 20 | """Set up test environment.""" 21 | # Use a test database file 22 | self.original_db_file = db.DB_FILE 23 | semantic.DB_FILE = Path("test_emails.duckdb") 24 | db.DB_FILE = Path("test_emails.duckdb") 25 | db.init_db() 26 | 27 | # Mock the embedding model 28 | self.model_patcher = patch('semantic.model') 29 | self.mock_model = self.model_patcher.start() 30 | 31 | def tearDown(self): 32 | """Clean up after tests.""" 33 | # Remove test database 34 | if Path("test_emails.duckdb").exists(): 35 | os.remove("test_emails.duckdb") 36 | 37 | # Restore original DB file 38 | db.DB_FILE = self.original_db_file 39 | semantic.DB_FILE = self.original_db_file 40 | 41 | # Stop model patcher 42 | self.model_patcher.stop() 43 | 44 | def test_generate_embedding(self): 45 | """Test generating embeddings from text.""" 46 | # Setup mock to return a fixed embedding 47 | mock_embedding = np.array([0.1, 0.2, 0.3, 0.4], dtype=np.float32) 48 | self.mock_model.encode.return_value = mock_embedding 49 | 50 | # Call the function 51 | result = semantic.generate_embedding("test text") 52 | 53 | # Verify the model was called correctly 54 | self.mock_model.encode.assert_called_once_with("test text", show_progress_bar=False) 55 | 56 | # Verify result is bytes 57 | self.assertIsInstance(result, bytes) 58 | 59 | # Convert bytes back to array and verify it matches the original 60 | result_array = np.frombuffer(result, dtype=np.float32) 61 | np.testing.assert_array_equal(result_array, mock_embedding) 62 | 63 | def test_cosine_similarity(self): 64 | """Test cosine similarity calculation.""" 65 | # Create test vectors 66 | vec1 = np.array([1.0, 0.0, 0.0], dtype=np.float32) # Unit vector along x 67 | vec2 = np.array([0.0, 1.0, 0.0], dtype=np.float32) # Unit vector along y 68 | vec3 = np.array([1.0, 1.0, 0.0], dtype=np.float32) # 45 degrees between x and y 69 | 70 | # Test orthogonal vectors (90 degrees, cos=0) 71 | similarity = semantic.cosine_similarity(vec1, vec2) 72 | self.assertAlmostEqual(similarity, 0.0, places=6) 73 | 74 | # Test identical vectors (0 degrees, cos=1) 75 | similarity = semantic.cosine_similarity(vec1, vec1) 76 | self.assertAlmostEqual(similarity, 1.0, places=6) 77 | 78 | # Test 45 degree angle (cos=0.7071) 79 | norm_vec3 = vec3 / np.linalg.norm(vec3) # Normalize vec3 80 | similarity = semantic.cosine_similarity(vec1, vec3) 81 | self.assertAlmostEqual(similarity, 0.7071, places=4) 82 | 83 | # Test with None vectors 84 | self.assertEqual(semantic.cosine_similarity(None, vec1), 0.0) 85 | self.assertEqual(semantic.cosine_similarity(vec1, None), 0.0) 86 | 87 | def test_semantic_search(self): 88 | """Test semantic search functionality.""" 89 | # Setup mock embeddings 90 | query_embedding = np.array([1.0, 0.0, 0.0], dtype=np.float32) 91 | email1_embedding = np.array([0.9, 0.1, 0.0], dtype=np.float32) # Very similar to query 92 | email2_embedding = np.array([0.2, 0.8, 0.0], dtype=np.float32) # Less similar 93 | email3_embedding = np.array([0.0, 1.0, 0.0], dtype=np.float32) # Not similar 94 | 95 | # Mock the text_to_embedding function 96 | with patch('semantic.text_to_embedding') as mock_text_to_embedding: 97 | mock_text_to_embedding.return_value = query_embedding 98 | 99 | # Mock the database connection and query 100 | with patch('duckdb.connect') as mock_connect: 101 | mock_conn = MagicMock() 102 | mock_connect.return_value.__enter__.return_value = mock_conn 103 | 104 | # Mock the result of the query with three emails 105 | mock_conn.execute.return_value.fetchall.return_value = [ 106 | (1, 'config1', 'Email 1', 'Body 1', 'sender1@example.com', 'recipient1@example.com', 107 | None, None, '2025-04-15 12:00:00', None, email1_embedding.tobytes()), 108 | (2, 'config1', 'Email 2', 'Body 2', 'sender2@example.com', 'recipient2@example.com', 109 | None, None, '2025-04-16 12:00:00', None, email2_embedding.tobytes()), 110 | (3, 'config2', 'Email 3', 'Body 3', 'sender3@example.com', 'recipient3@example.com', 111 | None, None, '2025-04-17 12:00:00', None, email3_embedding.tobytes()), 112 | ] 113 | 114 | # Mock the description to return column names 115 | mock_conn.description = [ 116 | ('id', None), ('config_name', None), ('subject', None), ('body', None), 117 | ('sender', None), ('recipients', None), ('cc', None), ('bcc', None), 118 | ('date', None), ('raw_content', None), ('embedding', None) 119 | ] 120 | 121 | # Call semantic_search 122 | results = semantic.semantic_search('test query', similarity_threshold=0.5) 123 | 124 | # Should return 2 results (emails 1 and 2) since email 3 is below threshold 125 | self.assertEqual(len(results), 2) 126 | 127 | # First result should be email 1 (most similar) 128 | self.assertEqual(results[0]['id'], 1) 129 | self.assertEqual(results[0]['subject'], 'Email 1') 130 | 131 | # Second result should be email 2 (less similar) 132 | self.assertEqual(results[1]['id'], 2) 133 | self.assertEqual(results[1]['subject'], 'Email 2') 134 | 135 | # Email 3 should not be in results (below threshold) 136 | self.assertTrue(all(r['id'] != 3 for r in results)) 137 | 138 | # Verify scores are in descending order and above threshold 139 | self.assertTrue(results[0]['similarity_score'] > results[1]['similarity_score']) 140 | self.assertTrue(results[0]['similarity_score'] >= 0.5) 141 | self.assertTrue(results[1]['similarity_score'] >= 0.5) 142 | 143 | if __name__ == "__main__": 144 | unittest.main() -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "annotated-types" 6 | version = "0.7.0" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 11 | ] 12 | 13 | [[package]] 14 | name = "anyio" 15 | version = "4.9.0" 16 | source = { registry = "https://pypi.org/simple" } 17 | dependencies = [ 18 | { name = "idna" }, 19 | { name = "sniffio" }, 20 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 21 | ] 22 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 23 | wheels = [ 24 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 25 | ] 26 | 27 | [[package]] 28 | name = "asyncio" 29 | version = "3.4.3" 30 | source = { registry = "https://pypi.org/simple" } 31 | sdist = { url = "https://files.pythonhosted.org/packages/da/54/054bafaf2c0fb8473d423743e191fcdf49b2c1fd5e9af3524efbe097bafd/asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41", size = 204411 } 32 | wheels = [ 33 | { url = "https://files.pythonhosted.org/packages/22/74/07679c5b9f98a7cb0fc147b1ef1cc1853bc07a4eb9cb5731e24732c5f773/asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d", size = 101767 }, 34 | ] 35 | 36 | [[package]] 37 | name = "certifi" 38 | version = "2025.1.31" 39 | source = { registry = "https://pypi.org/simple" } 40 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } 41 | wheels = [ 42 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, 43 | ] 44 | 45 | [[package]] 46 | name = "charset-normalizer" 47 | version = "3.4.1" 48 | source = { registry = "https://pypi.org/simple" } 49 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 50 | wheels = [ 51 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 52 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 53 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 54 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 55 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 56 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 57 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 58 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 59 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 60 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 61 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 62 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 63 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 64 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 65 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 66 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 67 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 68 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 69 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 70 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 71 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 72 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 73 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 74 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 75 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 76 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 77 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 78 | ] 79 | 80 | [[package]] 81 | name = "click" 82 | version = "8.1.8" 83 | source = { registry = "https://pypi.org/simple" } 84 | dependencies = [ 85 | { name = "colorama", marker = "platform_system == 'Windows'" }, 86 | ] 87 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 88 | wheels = [ 89 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 90 | ] 91 | 92 | [[package]] 93 | name = "colorama" 94 | version = "0.4.6" 95 | source = { registry = "https://pypi.org/simple" } 96 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 97 | wheels = [ 98 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 99 | ] 100 | 101 | [[package]] 102 | name = "duckdb" 103 | version = "1.2.2" 104 | source = { registry = "https://pypi.org/simple" } 105 | sdist = { url = "https://files.pythonhosted.org/packages/28/b8/0f86278684fb7a1fac7c0c869fc6d68ed005cdc91c963eb4373e0551bc0a/duckdb-1.2.2.tar.gz", hash = "sha256:1e53555dece49201df08645dbfa4510c86440339889667702f936b7d28d39e43", size = 11595514 } 106 | wheels = [ 107 | { url = "https://files.pythonhosted.org/packages/77/25/549f68e55e1b455bd2daf2e5fc912000a3139fe0395111b3d49b23a2cec1/duckdb-1.2.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f745379f44ad302560688855baaed9739c03b37a331338eda6a4ac655e4eb42f", size = 15271882 }, 108 | { url = "https://files.pythonhosted.org/packages/f6/84/13de7bf9056dcc7a346125d9a9f0f26f76c633db6b54052738f78f828538/duckdb-1.2.2-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:087713fc5958cae5eb59097856b3deaae0def021660c8f2052ec83fa8345174a", size = 31964873 }, 109 | { url = "https://files.pythonhosted.org/packages/0f/53/c8d2d56a801b7843ea87f8533a3634e6b38f06910098a266f8a096bd4c61/duckdb-1.2.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:a1f96395319c447a31b9477881bd84b4cb8323d6f86f21ceaef355d22dd90623", size = 16800653 }, 110 | { url = "https://files.pythonhosted.org/packages/bb/36/e25791d879fb93b92a56bf481ce11949ab19109103ae2ba12d64e49355d9/duckdb-1.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aba3bc0acf4f8d52b94f7746c3b0007b78b517676d482dc516d63f48f967baf", size = 18735524 }, 111 | { url = "https://files.pythonhosted.org/packages/d7/46/4745aa10a1e460f4c8b473eddaffe2c783ac5280e1e5929dd84bd1a1acde/duckdb-1.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5c1556775a9ebaa49b5c8d64718f155ac3e05b34a49e9c99443cf105e8b0371", size = 20210314 }, 112 | { url = "https://files.pythonhosted.org/packages/ff/0d/8563fc5ece36252e3d07dd3d29c7a0a034dcf62f14bed7cdc016d95adcbe/duckdb-1.2.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d625cc7d2faacfb2fc83ebbe001ae75dda175b3d8dce6a51a71c199ffac3627a", size = 18755134 }, 113 | { url = "https://files.pythonhosted.org/packages/11/f1/b7ade7d980eee4fb3ad7469ccf23adb3668a9a28cf3989b24418392d3786/duckdb-1.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:73263f81545c5cb4360fbaf7b22a493e55ddf88fadbe639c43efb7bc8d7554c4", size = 22294397 }, 114 | { url = "https://files.pythonhosted.org/packages/eb/c9/896e8ced7b408df81e015fe0c6497cda46c92d9dfc8bf84b6d13f5dad473/duckdb-1.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b1c0c4d737fd2ab9681e4e78b9f361e0a827916a730e84fa91e76dca451b14d5", size = 11370381 }, 115 | { url = "https://files.pythonhosted.org/packages/41/31/5e2f68cbd000137f6ed52092ad83a8e9c09eca70c59e0b4c5eb679709997/duckdb-1.2.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:fb9a2c77236fae079185a990434cb9d8432902488ba990235c702fc2692d2dcd", size = 15272507 }, 116 | { url = "https://files.pythonhosted.org/packages/d2/15/aa9078fc897e744e077c0c1510e34db4c809de1d51ddb5cb62e1f9c61312/duckdb-1.2.2-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:d8bb89e580cb9a3aaf42e4555bf265d3db9446abfb118e32150e1a5dfa4b5b15", size = 31965548 }, 117 | { url = "https://files.pythonhosted.org/packages/9f/28/943773d44fd97055c59b58dde9182733661c2b6e3b3549f15dc26b2e139e/duckdb-1.2.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:88916d7f0532dc926bed84b50408c00dcbe6d2097d0de93c3ff647d8d57b4f83", size = 16800600 }, 118 | { url = "https://files.pythonhosted.org/packages/39/51/2caf01e7791e490290798c8c155d4d702ed61d69e815915b42e72b3e7473/duckdb-1.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30bece4f58a6c7bb0944a02dd1dc6de435a9daf8668fa31a9fe3a9923b20bd65", size = 18735886 }, 119 | { url = "https://files.pythonhosted.org/packages/87/0c/48ae1d485725af3a452303af409a9022d751ecab260cb9ca2f8c9fb670bc/duckdb-1.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd2c6373b8b54474724c2119f6939c4568c428e1d0be5bcb1f4e3d7f1b7c8bb", size = 20210481 }, 120 | { url = "https://files.pythonhosted.org/packages/69/c7/95fcd7bde0f754ea6700208d36b845379cbd2b28779c0eff4dd4a7396369/duckdb-1.2.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72f688a8b0df7030c5a28ca6072817c1f090979e08d28ee5912dee37c26a7d0c", size = 18756619 }, 121 | { url = "https://files.pythonhosted.org/packages/ad/1b/c9eab9e84d4a70dd5f7e2a93dd6e9d7b4d868d3df755cd58b572d82d6c5d/duckdb-1.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26e9c349f56f7c99341b5c79bbaff5ba12a5414af0261e79bf1a6a2693f152f6", size = 22294667 }, 122 | { url = "https://files.pythonhosted.org/packages/3f/3d/ce68db53084746a4a62695a4cb064e44ce04123f8582bb3afbf6ee944e16/duckdb-1.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1aec7102670e59d83512cf47d32a6c77a79df9df0294c5e4d16b6259851e2e9", size = 11370206 }, 123 | ] 124 | 125 | [[package]] 126 | name = "filelock" 127 | version = "3.18.0" 128 | source = { registry = "https://pypi.org/simple" } 129 | sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } 130 | wheels = [ 131 | { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, 132 | ] 133 | 134 | [[package]] 135 | name = "fsspec" 136 | version = "2025.3.2" 137 | source = { registry = "https://pypi.org/simple" } 138 | sdist = { url = "https://files.pythonhosted.org/packages/45/d8/8425e6ba5fcec61a1d16e41b1b71d2bf9344f1fe48012c2b48b9620feae5/fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6", size = 299281 } 139 | wheels = [ 140 | { url = "https://files.pythonhosted.org/packages/44/4b/e0cfc1a6f17e990f3e64b7d941ddc4acdc7b19d6edd51abf495f32b1a9e4/fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711", size = 194435 }, 141 | ] 142 | 143 | [[package]] 144 | name = "h11" 145 | version = "0.14.0" 146 | source = { registry = "https://pypi.org/simple" } 147 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 148 | wheels = [ 149 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 150 | ] 151 | 152 | [[package]] 153 | name = "httpcore" 154 | version = "1.0.8" 155 | source = { registry = "https://pypi.org/simple" } 156 | dependencies = [ 157 | { name = "certifi" }, 158 | { name = "h11" }, 159 | ] 160 | sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 } 161 | wheels = [ 162 | { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 }, 163 | ] 164 | 165 | [[package]] 166 | name = "httpx" 167 | version = "0.28.1" 168 | source = { registry = "https://pypi.org/simple" } 169 | dependencies = [ 170 | { name = "anyio" }, 171 | { name = "certifi" }, 172 | { name = "httpcore" }, 173 | { name = "idna" }, 174 | ] 175 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 176 | wheels = [ 177 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 178 | ] 179 | 180 | [[package]] 181 | name = "httpx-sse" 182 | version = "0.4.0" 183 | source = { registry = "https://pypi.org/simple" } 184 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 185 | wheels = [ 186 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 187 | ] 188 | 189 | [[package]] 190 | name = "huggingface-hub" 191 | version = "0.30.2" 192 | source = { registry = "https://pypi.org/simple" } 193 | dependencies = [ 194 | { name = "filelock" }, 195 | { name = "fsspec" }, 196 | { name = "packaging" }, 197 | { name = "pyyaml" }, 198 | { name = "requests" }, 199 | { name = "tqdm" }, 200 | { name = "typing-extensions" }, 201 | ] 202 | sdist = { url = "https://files.pythonhosted.org/packages/df/22/8eb91736b1dcb83d879bd49050a09df29a57cc5cd9f38e48a4b1c45ee890/huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466", size = 400868 } 203 | wheels = [ 204 | { url = "https://files.pythonhosted.org/packages/93/27/1fb384a841e9661faad1c31cbfa62864f59632e876df5d795234da51c395/huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28", size = 481433 }, 205 | ] 206 | 207 | [[package]] 208 | name = "idna" 209 | version = "3.10" 210 | source = { registry = "https://pypi.org/simple" } 211 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 212 | wheels = [ 213 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 214 | ] 215 | 216 | [[package]] 217 | name = "imapclient" 218 | version = "3.0.1" 219 | source = { registry = "https://pypi.org/simple" } 220 | sdist = { url = "https://files.pythonhosted.org/packages/b6/63/0eea51c9c263c18021cdc5866def55c98393f3bd74bbb8e3053e36f0f81a/IMAPClient-3.0.1.zip", hash = "sha256:78e6d62fbfbbe233e1f0e0e993160fd665eb1fd35973acddc61c15719b22bc02", size = 244222 } 221 | wheels = [ 222 | { url = "https://files.pythonhosted.org/packages/de/8a/d1364c1c6d8f53ea390e8f1c6da220a4f9ee478ac8a473ae0669a2fb6f51/IMAPClient-3.0.1-py2.py3-none-any.whl", hash = "sha256:d77d77caa4123e0233b5cf2b9c54a078522e63270b88d3f48653a28637fd8828", size = 182490 }, 223 | ] 224 | 225 | [[package]] 226 | name = "jinja2" 227 | version = "3.1.6" 228 | source = { registry = "https://pypi.org/simple" } 229 | dependencies = [ 230 | { name = "markupsafe" }, 231 | ] 232 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } 233 | wheels = [ 234 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, 235 | ] 236 | 237 | [[package]] 238 | name = "joblib" 239 | version = "1.4.2" 240 | source = { registry = "https://pypi.org/simple" } 241 | sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 } 242 | wheels = [ 243 | { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 }, 244 | ] 245 | 246 | [[package]] 247 | name = "markdown-it-py" 248 | version = "3.0.0" 249 | source = { registry = "https://pypi.org/simple" } 250 | dependencies = [ 251 | { name = "mdurl" }, 252 | ] 253 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 254 | wheels = [ 255 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 256 | ] 257 | 258 | [[package]] 259 | name = "markupsafe" 260 | version = "3.0.2" 261 | source = { registry = "https://pypi.org/simple" } 262 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 263 | wheels = [ 264 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, 265 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, 266 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, 267 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, 268 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, 269 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, 270 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, 271 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, 272 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 273 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 274 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, 275 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, 276 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, 277 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, 278 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, 279 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, 280 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, 281 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, 282 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 283 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 284 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, 285 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, 286 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, 287 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, 288 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, 289 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, 290 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, 291 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, 292 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 293 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 294 | ] 295 | 296 | [[package]] 297 | name = "mcp" 298 | version = "1.6.0" 299 | source = { registry = "https://pypi.org/simple" } 300 | dependencies = [ 301 | { name = "anyio" }, 302 | { name = "httpx" }, 303 | { name = "httpx-sse" }, 304 | { name = "pydantic" }, 305 | { name = "pydantic-settings" }, 306 | { name = "sse-starlette" }, 307 | { name = "starlette" }, 308 | { name = "uvicorn" }, 309 | ] 310 | sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 } 311 | wheels = [ 312 | { url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 }, 313 | ] 314 | 315 | [package.optional-dependencies] 316 | cli = [ 317 | { name = "python-dotenv" }, 318 | { name = "typer" }, 319 | ] 320 | 321 | [[package]] 322 | name = "mcp-email-client" 323 | version = "0.1.0" 324 | source = { editable = "." } 325 | dependencies = [ 326 | { name = "asyncio" }, 327 | { name = "duckdb" }, 328 | { name = "imapclient" }, 329 | { name = "mcp", extra = ["cli"] }, 330 | { name = "numpy" }, 331 | { name = "pydantic" }, 332 | { name = "sentence-transformers" }, 333 | ] 334 | 335 | [package.metadata] 336 | requires-dist = [ 337 | { name = "asyncio", specifier = ">=3.4.3" }, 338 | { name = "duckdb", specifier = ">=1.2.2" }, 339 | { name = "imapclient", specifier = ">=3.0.1" }, 340 | { name = "mcp", extras = ["cli"], specifier = ">=1.3.0" }, 341 | { name = "numpy", specifier = ">=2.2.4" }, 342 | { name = "pydantic", specifier = ">=2.10.6" }, 343 | { name = "sentence-transformers", specifier = ">=4.1.0" }, 344 | ] 345 | 346 | [[package]] 347 | name = "mdurl" 348 | version = "0.1.2" 349 | source = { registry = "https://pypi.org/simple" } 350 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 351 | wheels = [ 352 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 353 | ] 354 | 355 | [[package]] 356 | name = "mpmath" 357 | version = "1.3.0" 358 | source = { registry = "https://pypi.org/simple" } 359 | sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } 360 | wheels = [ 361 | { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, 362 | ] 363 | 364 | [[package]] 365 | name = "networkx" 366 | version = "3.4.2" 367 | source = { registry = "https://pypi.org/simple" } 368 | sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } 369 | wheels = [ 370 | { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, 371 | ] 372 | 373 | [[package]] 374 | name = "numpy" 375 | version = "2.2.5" 376 | source = { registry = "https://pypi.org/simple" } 377 | sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920 } 378 | wheels = [ 379 | { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633 }, 380 | { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123 }, 381 | { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817 }, 382 | { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066 }, 383 | { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277 }, 384 | { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742 }, 385 | { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825 }, 386 | { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600 }, 387 | { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626 }, 388 | { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715 }, 389 | { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102 }, 390 | { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709 }, 391 | { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173 }, 392 | { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502 }, 393 | { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417 }, 394 | { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807 }, 395 | { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611 }, 396 | { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747 }, 397 | { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594 }, 398 | { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356 }, 399 | { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778 }, 400 | { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279 }, 401 | { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247 }, 402 | { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087 }, 403 | { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964 }, 404 | { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214 }, 405 | { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788 }, 406 | { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672 }, 407 | { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102 }, 408 | { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096 }, 409 | ] 410 | 411 | [[package]] 412 | name = "nvidia-cublas-cu12" 413 | version = "12.4.5.8" 414 | source = { registry = "https://pypi.org/simple" } 415 | wheels = [ 416 | { url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 }, 417 | { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, 418 | ] 419 | 420 | [[package]] 421 | name = "nvidia-cuda-cupti-cu12" 422 | version = "12.4.127" 423 | source = { registry = "https://pypi.org/simple" } 424 | wheels = [ 425 | { url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 }, 426 | { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, 427 | ] 428 | 429 | [[package]] 430 | name = "nvidia-cuda-nvrtc-cu12" 431 | version = "12.4.127" 432 | source = { registry = "https://pypi.org/simple" } 433 | wheels = [ 434 | { url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 }, 435 | { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, 436 | ] 437 | 438 | [[package]] 439 | name = "nvidia-cuda-runtime-cu12" 440 | version = "12.4.127" 441 | source = { registry = "https://pypi.org/simple" } 442 | wheels = [ 443 | { url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 }, 444 | { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, 445 | ] 446 | 447 | [[package]] 448 | name = "nvidia-cudnn-cu12" 449 | version = "9.1.0.70" 450 | source = { registry = "https://pypi.org/simple" } 451 | dependencies = [ 452 | { name = "nvidia-cublas-cu12" }, 453 | ] 454 | wheels = [ 455 | { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, 456 | ] 457 | 458 | [[package]] 459 | name = "nvidia-cufft-cu12" 460 | version = "11.2.1.3" 461 | source = { registry = "https://pypi.org/simple" } 462 | dependencies = [ 463 | { name = "nvidia-nvjitlink-cu12" }, 464 | ] 465 | wheels = [ 466 | { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, 467 | { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, 468 | ] 469 | 470 | [[package]] 471 | name = "nvidia-curand-cu12" 472 | version = "10.3.5.147" 473 | source = { registry = "https://pypi.org/simple" } 474 | wheels = [ 475 | { url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 }, 476 | { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, 477 | ] 478 | 479 | [[package]] 480 | name = "nvidia-cusolver-cu12" 481 | version = "11.6.1.9" 482 | source = { registry = "https://pypi.org/simple" } 483 | dependencies = [ 484 | { name = "nvidia-cublas-cu12" }, 485 | { name = "nvidia-cusparse-cu12" }, 486 | { name = "nvidia-nvjitlink-cu12" }, 487 | ] 488 | wheels = [ 489 | { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, 490 | { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, 491 | ] 492 | 493 | [[package]] 494 | name = "nvidia-cusparse-cu12" 495 | version = "12.3.1.170" 496 | source = { registry = "https://pypi.org/simple" } 497 | dependencies = [ 498 | { name = "nvidia-nvjitlink-cu12" }, 499 | ] 500 | wheels = [ 501 | { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, 502 | { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, 503 | ] 504 | 505 | [[package]] 506 | name = "nvidia-cusparselt-cu12" 507 | version = "0.6.2" 508 | source = { registry = "https://pypi.org/simple" } 509 | wheels = [ 510 | { url = "https://files.pythonhosted.org/packages/98/8e/675498726c605c9441cf46653bd29cb1b8666da1fb1469ffa25f67f20c58/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:067a7f6d03ea0d4841c85f0c6f1991c5dda98211f6302cb83a4ab234ee95bef8", size = 149422781 }, 511 | { url = "https://files.pythonhosted.org/packages/78/a8/bcbb63b53a4b1234feeafb65544ee55495e1bb37ec31b999b963cbccfd1d/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9", size = 150057751 }, 512 | ] 513 | 514 | [[package]] 515 | name = "nvidia-nccl-cu12" 516 | version = "2.21.5" 517 | source = { registry = "https://pypi.org/simple" } 518 | wheels = [ 519 | { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414 }, 520 | ] 521 | 522 | [[package]] 523 | name = "nvidia-nvjitlink-cu12" 524 | version = "12.4.127" 525 | source = { registry = "https://pypi.org/simple" } 526 | wheels = [ 527 | { url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 }, 528 | { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, 529 | ] 530 | 531 | [[package]] 532 | name = "nvidia-nvtx-cu12" 533 | version = "12.4.127" 534 | source = { registry = "https://pypi.org/simple" } 535 | wheels = [ 536 | { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, 537 | { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, 538 | ] 539 | 540 | [[package]] 541 | name = "packaging" 542 | version = "25.0" 543 | source = { registry = "https://pypi.org/simple" } 544 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } 545 | wheels = [ 546 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, 547 | ] 548 | 549 | [[package]] 550 | name = "pillow" 551 | version = "11.2.1" 552 | source = { registry = "https://pypi.org/simple" } 553 | sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 } 554 | wheels = [ 555 | { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185 }, 556 | { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306 }, 557 | { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121 }, 558 | { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707 }, 559 | { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921 }, 560 | { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523 }, 561 | { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836 }, 562 | { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390 }, 563 | { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309 }, 564 | { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768 }, 565 | { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087 }, 566 | { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, 567 | { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, 568 | { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, 569 | { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, 570 | { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, 571 | { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, 572 | { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, 573 | { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, 574 | { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, 575 | { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, 576 | { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, 577 | { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, 578 | { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, 579 | { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, 580 | { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, 581 | { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, 582 | { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, 583 | { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, 584 | { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, 585 | { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, 586 | { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, 587 | { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, 588 | ] 589 | 590 | [[package]] 591 | name = "pydantic" 592 | version = "2.11.3" 593 | source = { registry = "https://pypi.org/simple" } 594 | dependencies = [ 595 | { name = "annotated-types" }, 596 | { name = "pydantic-core" }, 597 | { name = "typing-extensions" }, 598 | { name = "typing-inspection" }, 599 | ] 600 | sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } 601 | wheels = [ 602 | { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, 603 | ] 604 | 605 | [[package]] 606 | name = "pydantic-core" 607 | version = "2.33.1" 608 | source = { registry = "https://pypi.org/simple" } 609 | dependencies = [ 610 | { name = "typing-extensions" }, 611 | ] 612 | sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } 613 | wheels = [ 614 | { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, 615 | { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, 616 | { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, 617 | { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, 618 | { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, 619 | { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, 620 | { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, 621 | { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, 622 | { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, 623 | { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, 624 | { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, 625 | { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, 626 | { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, 627 | { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, 628 | { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, 629 | { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, 630 | { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, 631 | { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, 632 | { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, 633 | { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, 634 | { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, 635 | { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, 636 | { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, 637 | { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, 638 | { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, 639 | { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, 640 | { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, 641 | { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, 642 | { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, 643 | { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, 644 | { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, 645 | ] 646 | 647 | [[package]] 648 | name = "pydantic-settings" 649 | version = "2.9.1" 650 | source = { registry = "https://pypi.org/simple" } 651 | dependencies = [ 652 | { name = "pydantic" }, 653 | { name = "python-dotenv" }, 654 | { name = "typing-inspection" }, 655 | ] 656 | sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 } 657 | wheels = [ 658 | { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, 659 | ] 660 | 661 | [[package]] 662 | name = "pygments" 663 | version = "2.19.1" 664 | source = { registry = "https://pypi.org/simple" } 665 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 666 | wheels = [ 667 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 668 | ] 669 | 670 | [[package]] 671 | name = "python-dotenv" 672 | version = "1.1.0" 673 | source = { registry = "https://pypi.org/simple" } 674 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } 675 | wheels = [ 676 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, 677 | ] 678 | 679 | [[package]] 680 | name = "pyyaml" 681 | version = "6.0.2" 682 | source = { registry = "https://pypi.org/simple" } 683 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 684 | wheels = [ 685 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 686 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 687 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 688 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 689 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 690 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 691 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 692 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 693 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 694 | { 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 }, 695 | { 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 }, 696 | { 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 }, 697 | { 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 }, 698 | { 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 }, 699 | { 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 }, 700 | { 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 }, 701 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 702 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 703 | ] 704 | 705 | [[package]] 706 | name = "regex" 707 | version = "2024.11.6" 708 | source = { registry = "https://pypi.org/simple" } 709 | sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } 710 | wheels = [ 711 | { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, 712 | { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, 713 | { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, 714 | { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, 715 | { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, 716 | { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, 717 | { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, 718 | { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, 719 | { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, 720 | { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, 721 | { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, 722 | { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, 723 | { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, 724 | { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, 725 | { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, 726 | { 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 }, 727 | { 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 }, 728 | { 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 }, 729 | { 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 }, 730 | { 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 }, 731 | { 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 }, 732 | { 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 }, 733 | { 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 }, 734 | { 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 }, 735 | { 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 }, 736 | { 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 }, 737 | { 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 }, 738 | { 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 }, 739 | { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, 740 | { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, 741 | ] 742 | 743 | [[package]] 744 | name = "requests" 745 | version = "2.32.3" 746 | source = { registry = "https://pypi.org/simple" } 747 | dependencies = [ 748 | { name = "certifi" }, 749 | { name = "charset-normalizer" }, 750 | { name = "idna" }, 751 | { name = "urllib3" }, 752 | ] 753 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 754 | wheels = [ 755 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 756 | ] 757 | 758 | [[package]] 759 | name = "rich" 760 | version = "14.0.0" 761 | source = { registry = "https://pypi.org/simple" } 762 | dependencies = [ 763 | { name = "markdown-it-py" }, 764 | { name = "pygments" }, 765 | ] 766 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } 767 | wheels = [ 768 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, 769 | ] 770 | 771 | [[package]] 772 | name = "safetensors" 773 | version = "0.5.3" 774 | source = { registry = "https://pypi.org/simple" } 775 | sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } 776 | wheels = [ 777 | { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, 778 | { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, 779 | { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, 780 | { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, 781 | { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, 782 | { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, 783 | { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, 784 | { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, 785 | { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, 786 | { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, 787 | { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, 788 | { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, 789 | { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, 790 | { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, 791 | ] 792 | 793 | [[package]] 794 | name = "scikit-learn" 795 | version = "1.6.1" 796 | source = { registry = "https://pypi.org/simple" } 797 | dependencies = [ 798 | { name = "joblib" }, 799 | { name = "numpy" }, 800 | { name = "scipy" }, 801 | { name = "threadpoolctl" }, 802 | ] 803 | sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } 804 | wheels = [ 805 | { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, 806 | { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, 807 | { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, 808 | { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, 809 | { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, 810 | { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, 811 | { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, 812 | { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, 813 | { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, 814 | { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, 815 | { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, 816 | { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, 817 | { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, 818 | { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, 819 | ] 820 | 821 | [[package]] 822 | name = "scipy" 823 | version = "1.15.2" 824 | source = { registry = "https://pypi.org/simple" } 825 | dependencies = [ 826 | { name = "numpy" }, 827 | ] 828 | sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } 829 | wheels = [ 830 | { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, 831 | { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, 832 | { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, 833 | { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, 834 | { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, 835 | { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, 836 | { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, 837 | { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, 838 | { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, 839 | { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, 840 | { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, 841 | { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, 842 | { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, 843 | { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, 844 | { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, 845 | { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, 846 | { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, 847 | { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, 848 | { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, 849 | { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, 850 | { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, 851 | { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, 852 | { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, 853 | { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, 854 | { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, 855 | { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, 856 | { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, 857 | ] 858 | 859 | [[package]] 860 | name = "sentence-transformers" 861 | version = "4.1.0" 862 | source = { registry = "https://pypi.org/simple" } 863 | dependencies = [ 864 | { name = "huggingface-hub" }, 865 | { name = "pillow" }, 866 | { name = "scikit-learn" }, 867 | { name = "scipy" }, 868 | { name = "torch" }, 869 | { name = "tqdm" }, 870 | { name = "transformers" }, 871 | { name = "typing-extensions" }, 872 | ] 873 | sdist = { url = "https://files.pythonhosted.org/packages/73/84/b30d1b29ff58cfdff423e36a50efd622c8e31d7039b1a0d5e72066620da1/sentence_transformers-4.1.0.tar.gz", hash = "sha256:f125ffd1c727533e0eca5d4567de72f84728de8f7482834de442fd90c2c3d50b", size = 272420 } 874 | wheels = [ 875 | { url = "https://files.pythonhosted.org/packages/45/2d/1151b371f28caae565ad384fdc38198f1165571870217aedda230b9d7497/sentence_transformers-4.1.0-py3-none-any.whl", hash = "sha256:382a7f6be1244a100ce40495fb7523dbe8d71b3c10b299f81e6b735092b3b8ca", size = 345695 }, 876 | ] 877 | 878 | [[package]] 879 | name = "setuptools" 880 | version = "79.0.0" 881 | source = { registry = "https://pypi.org/simple" } 882 | sdist = { url = "https://files.pythonhosted.org/packages/7d/19/fecb7e2825616270f34512b3394cdcf6f45a79b5b6d94fdbd86a509e67b5/setuptools-79.0.0.tar.gz", hash = "sha256:9828422e7541213b0aacb6e10bbf9dd8febeaa45a48570e09b6d100e063fc9f9", size = 1367685 } 883 | wheels = [ 884 | { url = "https://files.pythonhosted.org/packages/cc/ea/d53f2f8897c46a36df085964d07761ea4c2d1f2cf92019693b6742b7aabb/setuptools-79.0.0-py3-none-any.whl", hash = "sha256:b9ab3a104bedb292323f53797b00864e10e434a3ab3906813a7169e4745b912a", size = 1256065 }, 885 | ] 886 | 887 | [[package]] 888 | name = "shellingham" 889 | version = "1.5.4" 890 | source = { registry = "https://pypi.org/simple" } 891 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 892 | wheels = [ 893 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 894 | ] 895 | 896 | [[package]] 897 | name = "sniffio" 898 | version = "1.3.1" 899 | source = { registry = "https://pypi.org/simple" } 900 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 901 | wheels = [ 902 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 903 | ] 904 | 905 | [[package]] 906 | name = "sse-starlette" 907 | version = "2.2.1" 908 | source = { registry = "https://pypi.org/simple" } 909 | dependencies = [ 910 | { name = "anyio" }, 911 | { name = "starlette" }, 912 | ] 913 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 } 914 | wheels = [ 915 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 }, 916 | ] 917 | 918 | [[package]] 919 | name = "starlette" 920 | version = "0.46.2" 921 | source = { registry = "https://pypi.org/simple" } 922 | dependencies = [ 923 | { name = "anyio" }, 924 | ] 925 | sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } 926 | wheels = [ 927 | { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, 928 | ] 929 | 930 | [[package]] 931 | name = "sympy" 932 | version = "1.13.1" 933 | source = { registry = "https://pypi.org/simple" } 934 | dependencies = [ 935 | { name = "mpmath" }, 936 | ] 937 | sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 } 938 | wheels = [ 939 | { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, 940 | ] 941 | 942 | [[package]] 943 | name = "threadpoolctl" 944 | version = "3.6.0" 945 | source = { registry = "https://pypi.org/simple" } 946 | sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } 947 | wheels = [ 948 | { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, 949 | ] 950 | 951 | [[package]] 952 | name = "tokenizers" 953 | version = "0.21.1" 954 | source = { registry = "https://pypi.org/simple" } 955 | dependencies = [ 956 | { name = "huggingface-hub" }, 957 | ] 958 | sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256 } 959 | wheels = [ 960 | { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767 }, 961 | { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555 }, 962 | { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541 }, 963 | { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058 }, 964 | { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278 }, 965 | { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253 }, 966 | { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225 }, 967 | { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874 }, 968 | { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448 }, 969 | { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877 }, 970 | { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645 }, 971 | { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380 }, 972 | { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, 973 | { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, 974 | ] 975 | 976 | [[package]] 977 | name = "torch" 978 | version = "2.6.0" 979 | source = { registry = "https://pypi.org/simple" } 980 | dependencies = [ 981 | { name = "filelock" }, 982 | { name = "fsspec" }, 983 | { name = "jinja2" }, 984 | { name = "networkx" }, 985 | { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 986 | { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 987 | { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 988 | { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 989 | { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 990 | { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 991 | { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 992 | { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 993 | { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 994 | { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 995 | { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 996 | { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 997 | { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 998 | { name = "setuptools" }, 999 | { name = "sympy" }, 1000 | { name = "triton", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, 1001 | { name = "typing-extensions" }, 1002 | ] 1003 | wheels = [ 1004 | { url = "https://files.pythonhosted.org/packages/e5/35/0c52d708144c2deb595cd22819a609f78fdd699b95ff6f0ebcd456e3c7c1/torch-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9", size = 766624563 }, 1005 | { url = "https://files.pythonhosted.org/packages/01/d6/455ab3fbb2c61c71c8842753b566012e1ed111e7a4c82e0e1c20d0c76b62/torch-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b789069020c5588c70d5c2158ac0aa23fd24a028f34a8b4fcb8fcb4d7efcf5fb", size = 95607867 }, 1006 | { url = "https://files.pythonhosted.org/packages/18/cf/ae99bd066571656185be0d88ee70abc58467b76f2f7c8bfeb48735a71fe6/torch-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e1448426d0ba3620408218b50aa6ada88aeae34f7a239ba5431f6c8774b1239", size = 204120469 }, 1007 | { url = "https://files.pythonhosted.org/packages/81/b4/605ae4173aa37fb5aa14605d100ff31f4f5d49f617928c9f486bb3aaec08/torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989", size = 66532538 }, 1008 | { url = "https://files.pythonhosted.org/packages/24/85/ead1349fc30fe5a32cadd947c91bda4a62fbfd7f8c34ee61f6398d38fb48/torch-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf", size = 766626191 }, 1009 | { url = "https://files.pythonhosted.org/packages/dd/b0/26f06f9428b250d856f6d512413e9e800b78625f63801cbba13957432036/torch-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a0d5e1b9874c1a6c25556840ab8920569a7a4137afa8a63a32cee0bc7d89bd4b", size = 95611439 }, 1010 | { url = "https://files.pythonhosted.org/packages/c2/9c/fc5224e9770c83faed3a087112d73147cd7c7bfb7557dcf9ad87e1dda163/torch-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:510c73251bee9ba02ae1cb6c9d4ee0907b3ce6020e62784e2d7598e0cfa4d6cc", size = 204126475 }, 1011 | { url = "https://files.pythonhosted.org/packages/88/8b/d60c0491ab63634763be1537ad488694d316ddc4a20eaadd639cedc53971/torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2", size = 66536783 }, 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "tqdm" 1016 | version = "4.67.1" 1017 | source = { registry = "https://pypi.org/simple" } 1018 | dependencies = [ 1019 | { name = "colorama", marker = "platform_system == 'Windows'" }, 1020 | ] 1021 | sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } 1022 | wheels = [ 1023 | { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "transformers" 1028 | version = "4.51.3" 1029 | source = { registry = "https://pypi.org/simple" } 1030 | dependencies = [ 1031 | { name = "filelock" }, 1032 | { name = "huggingface-hub" }, 1033 | { name = "numpy" }, 1034 | { name = "packaging" }, 1035 | { name = "pyyaml" }, 1036 | { name = "regex" }, 1037 | { name = "requests" }, 1038 | { name = "safetensors" }, 1039 | { name = "tokenizers" }, 1040 | { name = "tqdm" }, 1041 | ] 1042 | sdist = { url = "https://files.pythonhosted.org/packages/f1/11/7414d5bc07690002ce4d7553602107bf969af85144bbd02830f9fb471236/transformers-4.51.3.tar.gz", hash = "sha256:e292fcab3990c6defe6328f0f7d2004283ca81a7a07b2de9a46d67fd81ea1409", size = 8941266 } 1043 | wheels = [ 1044 | { url = "https://files.pythonhosted.org/packages/a9/b6/5257d04ae327b44db31f15cce39e6020cc986333c715660b1315a9724d82/transformers-4.51.3-py3-none-any.whl", hash = "sha256:fd3279633ceb2b777013234bbf0b4f5c2d23c4626b05497691f00cfda55e8a83", size = 10383940 }, 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "triton" 1049 | version = "3.2.0" 1050 | source = { registry = "https://pypi.org/simple" } 1051 | wheels = [ 1052 | { url = "https://files.pythonhosted.org/packages/06/00/59500052cb1cf8cf5316be93598946bc451f14072c6ff256904428eaf03c/triton-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c", size = 253159365 }, 1053 | { url = "https://files.pythonhosted.org/packages/c7/30/37a3384d1e2e9320331baca41e835e90a3767303642c7a80d4510152cbcf/triton-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0", size = 253154278 }, 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "typer" 1058 | version = "0.15.2" 1059 | source = { registry = "https://pypi.org/simple" } 1060 | dependencies = [ 1061 | { name = "click" }, 1062 | { name = "rich" }, 1063 | { name = "shellingham" }, 1064 | { name = "typing-extensions" }, 1065 | ] 1066 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } 1067 | wheels = [ 1068 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "typing-extensions" 1073 | version = "4.13.2" 1074 | source = { registry = "https://pypi.org/simple" } 1075 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } 1076 | wheels = [ 1077 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "typing-inspection" 1082 | version = "0.4.0" 1083 | source = { registry = "https://pypi.org/simple" } 1084 | dependencies = [ 1085 | { name = "typing-extensions" }, 1086 | ] 1087 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } 1088 | wheels = [ 1089 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "urllib3" 1094 | version = "2.4.0" 1095 | source = { registry = "https://pypi.org/simple" } 1096 | sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } 1097 | wheels = [ 1098 | { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "uvicorn" 1103 | version = "0.34.2" 1104 | source = { registry = "https://pypi.org/simple" } 1105 | dependencies = [ 1106 | { name = "click" }, 1107 | { name = "h11" }, 1108 | ] 1109 | sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815 } 1110 | wheels = [ 1111 | { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483 }, 1112 | ] 1113 | --------------------------------------------------------------------------------