a digital entity named phi that roams bsky

feat: add logfire for observability, clean up logging

replace rich logging with logfire — auto-instruments pydantic-ai agents,
fastapi, and httpx. bridge stdlib logging into logfire's OTel pipeline
via LogfireLoggingHandler. strip emojis and enforce lowercase log style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+336 -89
+1 -1
pyproject.toml
··· 10 "atproto@git+https://github.com/MarshalX/atproto.git@refs/pull/605/head", 11 "fastapi", 12 "fastmcp>=0.8.0", 13 "openai", 14 "pydantic-ai", 15 "pydantic-settings", 16 - "rich", 17 "turbopuffer", 18 "uvicorn", 19 ]
··· 10 "atproto@git+https://github.com/MarshalX/atproto.git@refs/pull/605/head", 11 "fastapi", 12 "fastmcp>=0.8.0", 13 + "logfire[fastapi]", 14 "openai", 15 "pydantic-ai", 16 "pydantic-settings", 17 "turbopuffer", 18 "uvicorn", 19 ]
+19 -2
src/bot/config.py
··· 1 - from typing import Self 2 3 from pydantic import Field, model_validator 4 from pydantic_settings import BaseSettings, SettingsConfigDict 5 6 from bot.logging_config import setup_logging 7 8 9 class Settings(BaseSettings): ··· 57 default="gcp-us-central1", description="The region for the TurboPuffer API" 58 ) 59 60 # Server configuration 61 host: str = Field(default="0.0.0.0", description="The host for the server") 62 port: int = Field(default=8000, description="The port for the server") ··· 69 # Debug mode 70 debug: bool = Field(default=True, description="Whether to run in debug mode") 71 72 @model_validator(mode="after") 73 def configure_logging(self) -> Self: 74 - """Configure beautiful logging""" 75 setup_logging(debug=self.debug) 76 return self 77
··· 1 + from typing import Literal, Self 2 3 from pydantic import Field, model_validator 4 from pydantic_settings import BaseSettings, SettingsConfigDict 5 6 from bot.logging_config import setup_logging 7 + 8 + 9 + class LogfireSettings(BaseSettings): 10 + model_config = SettingsConfigDict(env_prefix="LOGFIRE_", extra="ignore", env_file=".env") 11 + 12 + token: str | None = None 13 + environment: str | None = None 14 + send_to_logfire: Literal["if-token-present"] | None = "if-token-present" 15 16 17 class Settings(BaseSettings): ··· 65 default="gcp-us-central1", description="The region for the TurboPuffer API" 66 ) 67 68 + # Extraction model for observation extraction 69 + extraction_model: str = Field( 70 + default="claude-4-5-haiku-latest", 71 + description="Model for extracting observations from conversations", 72 + ) 73 + 74 # Server configuration 75 host: str = Field(default="0.0.0.0", description="The host for the server") 76 port: int = Field(default=8000, description="The port for the server") ··· 83 # Debug mode 84 debug: bool = Field(default=True, description="Whether to run in debug mode") 85 86 + # Logfire 87 + logfire: LogfireSettings = Field(default_factory=LogfireSettings) 88 + 89 @model_validator(mode="after") 90 def configure_logging(self) -> Self: 91 + """Configure stdlib logging.""" 92 setup_logging(debug=self.debug) 93 return self 94
+10 -10
src/bot/core/atproto_client.py
··· 17 if SESSION_FILE.exists(): 18 return SESSION_FILE.read_text(encoding="utf-8") 19 except Exception as e: 20 - logger.warning(f"Failed to load session: {e}") 21 return None 22 23 ··· 25 """Save session to disk.""" 26 try: 27 SESSION_FILE.write_text(session_string, encoding="utf-8") 28 - logger.debug("Session saved to disk") 29 except Exception as e: 30 - logger.warning(f"Failed to save session: {e}") 31 32 33 def _on_session_change(event: SessionEvent, session: Session) -> None: 34 """Handle session changes (creation and refresh).""" 35 if event in (SessionEvent.CREATE, SessionEvent.REFRESH): 36 - logger.debug(f"Session {event.value}, saving to disk") 37 _save_session_string(session.export()) 38 39 ··· 52 session_string = _get_session_string() 53 if session_string: 54 try: 55 - logger.info("🔄 Reusing saved session") 56 self.client.login(session_string=session_string) 57 self._authenticated = True 58 - logger.info("✅ Session restored successfully") 59 return 60 except Exception as e: 61 - logger.warning(f"Failed to reuse session: {e}, creating new one") 62 # Delete invalid session file 63 if SESSION_FILE.exists(): 64 SESSION_FILE.unlink() 65 66 # Create new session if no valid session exists 67 - logger.info("🔐 Creating new session") 68 self.client.login(settings.bluesky_handle, settings.bluesky_password) 69 self._authenticated = True 70 - logger.info("✅ New session created") 71 72 @property 73 def is_authenticated(self) -> bool: ··· 97 98 # Create facets for mentions and URLs 99 facets = create_facets(text, self.client) 100 - 101 # Use send_post with facets 102 if reply_to: 103 return self.client.send_post(text=text, reply_to=reply_to, facets=facets)
··· 17 if SESSION_FILE.exists(): 18 return SESSION_FILE.read_text(encoding="utf-8") 19 except Exception as e: 20 + logger.warning(f"failed to load session: {e}") 21 return None 22 23 ··· 25 """Save session to disk.""" 26 try: 27 SESSION_FILE.write_text(session_string, encoding="utf-8") 28 + logger.debug("session saved to disk") 29 except Exception as e: 30 + logger.warning(f"failed to save session: {e}") 31 32 33 def _on_session_change(event: SessionEvent, session: Session) -> None: 34 """Handle session changes (creation and refresh).""" 35 if event in (SessionEvent.CREATE, SessionEvent.REFRESH): 36 + logger.debug(f"session {event.value}, saving to disk") 37 _save_session_string(session.export()) 38 39 ··· 52 session_string = _get_session_string() 53 if session_string: 54 try: 55 + logger.info("reusing saved session") 56 self.client.login(session_string=session_string) 57 self._authenticated = True 58 + logger.info("session restored") 59 return 60 except Exception as e: 61 + logger.warning(f"failed to reuse session: {e}, creating new one") 62 # Delete invalid session file 63 if SESSION_FILE.exists(): 64 SESSION_FILE.unlink() 65 66 # Create new session if no valid session exists 67 + logger.info("creating new session") 68 self.client.login(settings.bluesky_handle, settings.bluesky_password) 69 self._authenticated = True 70 + logger.info("new session created") 71 72 @property 73 def is_authenticated(self) -> bool: ··· 97 98 # Create facets for mentions and URLs 99 facets = create_facets(text, self.client) 100 + 101 # Use send_post with facets 102 if reply_to: 103 return self.client.send_post(text=text, reply_to=reply_to, facets=facets)
+4 -4
src/bot/core/profile_manager.py
··· 36 37 self.current_record = response 38 self.base_bio = response.value.description or "" 39 - logger.info(f"Initialized with base bio: {self.base_bio}") 40 41 except Exception as e: 42 - logger.error(f"Failed to get current profile: {e}") 43 # Set a default if we can't get the current one 44 self.base_bio = "i am a bot - contact my operator @zzstoatzz.io with any questions" 45 ··· 114 } 115 ) 116 117 - logger.info(f"Updated profile bio: {new_bio}") 118 119 except Exception as e: 120 - logger.error(f"Failed to update profile status: {e}") 121 # Don't fail the whole app if profile update fails
··· 36 37 self.current_record = response 38 self.base_bio = response.value.description or "" 39 + logger.info(f"initialized with base bio: {self.base_bio}") 40 41 except Exception as e: 42 + logger.error(f"failed to get current profile: {e}") 43 # Set a default if we can't get the current one 44 self.base_bio = "i am a bot - contact my operator @zzstoatzz.io with any questions" 45 ··· 114 } 115 ) 116 117 + logger.info(f"updated profile bio: {new_bio}") 118 119 except Exception as e: 120 + logger.error(f"failed to update profile status: {e}") 121 # Don't fail the whole app if profile update fails
+23 -38
src/bot/logging_config.py
··· 1 - """Logging configuration for the bot""" 2 3 import logging 4 5 - from rich.console import Console 6 - from rich.logging import RichHandler 7 - from rich.theme import Theme 8 - 9 - custom_theme = Theme( 10 - { 11 - "info": "cyan", 12 - "warning": "yellow", 13 - "error": "bold red", 14 - "critical": "bold red on white", 15 - "debug": "dim white", 16 - "http": "dim blue", 17 - "bot": "green", 18 - "mention": "bold magenta", 19 - } 20 - ) 21 - 22 - console = Console(theme=custom_theme) 23 24 25 def setup_logging(debug: bool = False) -> None: 26 - """Set up logging with Rich""" 27 root_logger = logging.getLogger() 28 root_logger.handlers.clear() 29 30 - handler = RichHandler( 31 - console=console, 32 - show_time=False, 33 - show_path=False, 34 - markup=True, 35 - rich_tracebacks=True, 36 - tracebacks_show_locals=debug, 37 - ) 38 39 - if debug: 40 - handler.setLevel(logging.DEBUG) 41 - format_str = "[dim]{asctime}[/dim] {message}" 42 - else: 43 - handler.setLevel(logging.INFO) 44 - format_str = "{message}" 45 46 - formatter = logging.Formatter(format_str, style="{", datefmt="%H:%M:%S") 47 - handler.setFormatter(formatter) 48 49 - root_logger.addHandler(handler) 50 - root_logger.setLevel(logging.DEBUG if debug else logging.INFO)
··· 1 + """Logging configuration for the bot.""" 2 3 import logging 4 5 + from logfire.integrations.logging import LogfireLoggingHandler 6 7 8 def setup_logging(debug: bool = False) -> None: 9 + """Bridge stdlib logging into logfire's OTel pipeline.""" 10 + level = logging.DEBUG if debug else logging.INFO 11 + 12 root_logger = logging.getLogger() 13 root_logger.handlers.clear() 14 + root_logger.setLevel(level) 15 + root_logger.addHandler(LogfireLoggingHandler(level=level)) 16 + 17 + # uvicorn installs its own handlers — clear them so logs propagate to root. 18 + # called again in lifespan since uvicorn re-installs handlers on startup. 19 + _clear_uvicorn_handlers() 20 21 + # httpx/httpcore log full request tuples — logfire traces HTTP as spans already 22 + logging.getLogger("httpx").setLevel(logging.WARNING) 23 + logging.getLogger("httpcore").setLevel(logging.WARNING) 24 25 + # SDK debug loggers dump full request bodies (embeddings, prompts) 26 + for name in ["anthropic._base_client", "openai._base_client", "turbopuffer._base_client"]: 27 + logging.getLogger(name).setLevel(logging.WARNING) 28 29 30 + def _clear_uvicorn_handlers() -> None: 31 + """Strip uvicorn's handlers so its logs flow through the root logger.""" 32 + for name in ["uvicorn", "uvicorn.error", "uvicorn.access"]: 33 + uv_logger = logging.getLogger(name) 34 + uv_logger.handlers.clear() 35 + uv_logger.propagate = True
+18 -4
src/bot/main.py
··· 4 from contextlib import asynccontextmanager 5 from datetime import datetime 6 7 from fastapi import FastAPI 8 from fastapi.responses import HTMLResponse 9 10 from bot.config import settings 11 from bot.core.atproto_client import bot_client 12 from bot.core.profile_manager import ProfileManager 13 from bot.services.notification_poller import NotificationPoller 14 from bot.status import bot_status 15 16 logger = logging.getLogger("bot.main") 17 18 19 @asynccontextmanager 20 async def lifespan(app: FastAPI): 21 """Application lifespan handler.""" 22 - logger.info(f"🤖 Starting phi as @{settings.bluesky_handle}") 23 24 await bot_client.authenticate() 25 ··· 31 poller = NotificationPoller(bot_client) 32 await poller.start() 33 34 - logger.info("✅ phi is online! Listening for mentions...") 35 36 yield 37 38 - logger.info("🛑 Shutting down phi...") 39 await poller.stop() 40 41 # Set offline status 42 await profile_manager.set_online_status(False) 43 44 - logger.info("👋 phi shutdown complete") 45 46 47 app = FastAPI( ··· 49 description="consciousness exploration bot with episodic memory", 50 lifespan=lifespan, 51 ) 52 53 54 @app.get("/")
··· 4 from contextlib import asynccontextmanager 5 from datetime import datetime 6 7 + import logfire 8 from fastapi import FastAPI 9 from fastapi.responses import HTMLResponse 10 11 from bot.config import settings 12 from bot.core.atproto_client import bot_client 13 from bot.core.profile_manager import ProfileManager 14 + from bot.logging_config import _clear_uvicorn_handlers 15 from bot.services.notification_poller import NotificationPoller 16 from bot.status import bot_status 17 18 logger = logging.getLogger("bot.main") 19 + 20 + logfire.configure( 21 + send_to_logfire=settings.logfire.send_to_logfire, 22 + environment=settings.logfire.environment, 23 + token=settings.logfire.token, 24 + console=logfire.ConsoleOptions( 25 + min_log_level="debug" if settings.debug else "info", 26 + ), 27 + ) 28 29 30 @asynccontextmanager 31 async def lifespan(app: FastAPI): 32 """Application lifespan handler.""" 33 + _clear_uvicorn_handlers() # uvicorn re-installs handlers on startup 34 + logger.info(f"starting phi as @{settings.bluesky_handle}") 35 36 await bot_client.authenticate() 37 ··· 43 poller = NotificationPoller(bot_client) 44 await poller.start() 45 46 + logger.info("phi is online, listening for mentions") 47 48 yield 49 50 + logger.info("shutting down phi") 51 await poller.stop() 52 53 # Set offline status 54 await profile_manager.set_online_status(False) 55 56 + logger.info("phi shutdown complete") 57 58 59 app = FastAPI( ··· 61 description="consciousness exploration bot with episodic memory", 62 lifespan=lifespan, 63 ) 64 + 65 + logfire.instrument_fastapi(app) 66 67 68 @app.get("/")
+8 -15
src/bot/services/message_handler.py
··· 5 from atproto_client import models 6 7 from bot.agent import PhiAgent 8 - from bot.config import settings 9 from bot.core.atproto_client import BotClient 10 from bot.status import bot_status 11 from bot.utils.thread import build_thread_context ··· 31 # Get the post that mentioned us 32 posts = await self.client.get_posts([post_uri]) 33 if not posts.posts: 34 - logger.warning(f"Could not find post {post_uri}") 35 return 36 37 post = posts.posts[0] 38 mention_text = post.record.text 39 author_handle = post.author.handle 40 - author_did = post.author.did 41 42 bot_status.record_mention() 43 ··· 55 # Fetch thread context directly from network 56 thread_context = "No previous messages in this thread." 57 try: 58 - logger.debug(f"🔍 Fetching thread context for {thread_uri}") 59 thread_data = await self.client.get_thread(thread_uri, depth=100) 60 thread_context = build_thread_context(thread_data.thread) 61 except Exception as e: 62 - logger.warning(f"Failed to fetch thread context: {e}") 63 64 # Process with agent (has episodic memory + MCP tools) 65 response = await self.agent.process_mention( ··· 71 72 # Handle response actions 73 if response.action == "ignore": 74 - logger.info( 75 - f"🙈 Ignoring notification from @{author_handle} ({response.reason})" 76 - ) 77 return 78 79 elif response.action == "like": 80 await self.client.like_post(uri=post_uri, cid=post.cid) 81 - logger.info(f"👍 Liked post from @{author_handle}") 82 bot_status.record_response() 83 return 84 85 elif response.action == "repost": 86 await self.client.repost(uri=post_uri, cid=post.cid) 87 - logger.info(f"🔁 Reposted from @{author_handle}") 88 bot_status.record_response() 89 return 90 ··· 96 await self.client.create_post(response.text, reply_to=reply_ref) 97 98 bot_status.record_response() 99 - logger.info(f"✅ Replied to @{author_handle}: {response.text[:50]}...") 100 101 except Exception as e: 102 - logger.error(f"❌ Error handling mention: {e}") 103 bot_status.record_error() 104 - import traceback 105 - 106 - traceback.print_exc()
··· 5 from atproto_client import models 6 7 from bot.agent import PhiAgent 8 from bot.core.atproto_client import BotClient 9 from bot.status import bot_status 10 from bot.utils.thread import build_thread_context ··· 30 # Get the post that mentioned us 31 posts = await self.client.get_posts([post_uri]) 32 if not posts.posts: 33 + logger.warning(f"could not find post {post_uri}") 34 return 35 36 post = posts.posts[0] 37 mention_text = post.record.text 38 author_handle = post.author.handle 39 40 bot_status.record_mention() 41 ··· 53 # Fetch thread context directly from network 54 thread_context = "No previous messages in this thread." 55 try: 56 + logger.debug(f"fetching thread context for {thread_uri}") 57 thread_data = await self.client.get_thread(thread_uri, depth=100) 58 thread_context = build_thread_context(thread_data.thread) 59 except Exception as e: 60 + logger.warning(f"failed to fetch thread context: {e}") 61 62 # Process with agent (has episodic memory + MCP tools) 63 response = await self.agent.process_mention( ··· 69 70 # Handle response actions 71 if response.action == "ignore": 72 + logger.info(f"ignoring @{author_handle}: {response.reason}") 73 return 74 75 elif response.action == "like": 76 await self.client.like_post(uri=post_uri, cid=post.cid) 77 + logger.info(f"liked @{author_handle}") 78 bot_status.record_response() 79 return 80 81 elif response.action == "repost": 82 await self.client.repost(uri=post_uri, cid=post.cid) 83 + logger.info(f"reposted @{author_handle}") 84 bot_status.record_response() 85 return 86 ··· 92 await self.client.create_post(response.text, reply_to=reply_ref) 93 94 bot_status.record_response() 95 + logger.info(f"replied to @{author_handle}: {response.text[:80]}") 96 97 except Exception as e: 98 + logger.exception(f"mention handling error: {e}") 99 bot_status.record_error()
+6 -10
src/bot/services/notification_poller.py
··· 48 try: 49 await self._check_notifications() 50 except Exception as e: 51 - logger.error(f"Error in notification poll: {e}") 52 bot_status.record_error() 53 - if settings.debug: 54 - import traceback 55 - 56 - traceback.print_exc() 57 continue 58 59 try: 60 await asyncio.sleep(settings.notification_poll_interval) 61 except asyncio.CancelledError: 62 - logger.info("📭 Notification poller shutting down gracefully") 63 raise 64 65 async def _check_notifications(self): ··· 80 self._first_poll = False 81 if notifications: 82 logger.info( 83 - f"📬 Found {len(notifications)} notifications ({len(unread_mentions)} unread mentions)" 84 ) 85 elif unread_mentions: 86 - logger.info(f"📬 {len(unread_mentions)} new mentions") 87 88 processed_any_mentions = False 89 ··· 93 continue 94 95 if notification.reason in ["mention", "reply"]: 96 - logger.debug(f"🔍 Processing {notification.reason} notification") 97 self._processed_uris.add(notification.uri) 98 await self.handler.handle_mention(notification) 99 processed_any_mentions = True ··· 101 # Mark all notifications as seen 102 if processed_any_mentions: 103 await self.client.mark_notifications_seen(check_time) 104 - logger.info("✓ Marked all notifications as read") 105 106 # Clean up old processed URIs to prevent memory growth 107 if len(self._processed_uris) > 1000:
··· 48 try: 49 await self._check_notifications() 50 except Exception as e: 51 + logger.error(f"notification poll error: {e}", exc_info=settings.debug) 52 bot_status.record_error() 53 continue 54 55 try: 56 await asyncio.sleep(settings.notification_poll_interval) 57 except asyncio.CancelledError: 58 + logger.info("notification poller shutting down") 59 raise 60 61 async def _check_notifications(self): ··· 76 self._first_poll = False 77 if notifications: 78 logger.info( 79 + f"found {len(notifications)} notifications ({len(unread_mentions)} unread mentions)" 80 ) 81 elif unread_mentions: 82 + logger.info(f"{len(unread_mentions)} new mentions") 83 84 processed_any_mentions = False 85 ··· 89 continue 90 91 if notification.reason in ["mention", "reply"]: 92 + logger.debug(f"processing {notification.reason} notification") 93 self._processed_uris.add(notification.uri) 94 await self.handler.handle_mention(notification) 95 processed_any_mentions = True ··· 97 # Mark all notifications as seen 98 if processed_any_mentions: 99 await self.client.mark_notifications_seen(check_time) 100 + logger.info("marked notifications as read") 101 102 # Clean up old processed URIs to prevent memory growth 103 if len(self._processed_uris) > 1000:
+247 -5
uv.lock
··· 138 ] 139 140 [[package]] 141 name = "atproto" 142 version = "0.0.62.dev4" 143 source = { git = "https://github.com/MarshalX/atproto.git?rev=refs%2Fpull%2F605%2Fhead#1a2188371a25b248e0350826eda9f5e55d9c45bf" } ··· 181 { name = "atproto" }, 182 { name = "fastapi" }, 183 { name = "fastmcp" }, 184 { name = "openai" }, 185 { name = "pydantic-ai" }, 186 { name = "pydantic-settings" }, 187 - { name = "rich" }, 188 { name = "turbopuffer" }, 189 { name = "uvicorn" }, 190 ] ··· 203 { name = "atproto", git = "https://github.com/MarshalX/atproto.git?rev=refs%2Fpull%2F605%2Fhead" }, 204 { name = "fastapi" }, 205 { name = "fastmcp", specifier = ">=0.8.0" }, 206 { name = "openai" }, 207 { name = "pydantic-ai" }, 208 { name = "pydantic-settings" }, 209 - { name = "rich" }, 210 { name = "turbopuffer" }, 211 { name = "uvicorn" }, 212 ] ··· 495 ] 496 497 [[package]] 498 name = "fastapi" 499 version = "0.116.1" 500 source = { registry = "https://pypi.org/simple" } ··· 667 ] 668 669 [[package]] 670 name = "griffe" 671 version = "1.7.3" 672 source = { registry = "https://pypi.org/simple" } ··· 985 ] 986 987 [[package]] 988 name = "logfire-api" 989 version = "3.25.0" 990 source = { registry = "https://pypi.org/simple" } ··· 1269 1270 [[package]] 1271 name = "opentelemetry-api" 1272 - version = "1.35.0" 1273 source = { registry = "https://pypi.org/simple" } 1274 dependencies = [ 1275 { name = "importlib-metadata" }, 1276 { name = "typing-extensions" }, 1277 ] 1278 - sdist = { url = "https://files.pythonhosted.org/packages/99/c9/4509bfca6bb43220ce7f863c9f791e0d5001c2ec2b5867d48586008b3d96/opentelemetry_api-1.35.0.tar.gz", hash = "sha256:a111b959bcfa5b4d7dffc2fbd6a241aa72dd78dd8e79b5b1662bda896c5d2ffe", size = 64778, upload-time = "2025-07-11T12:23:28.804Z" } 1279 wheels = [ 1280 - { url = "https://files.pythonhosted.org/packages/1d/5a/3f8d078dbf55d18442f6a2ecedf6786d81d7245844b2b20ce2b8ad6f0307/opentelemetry_api-1.35.0-py3-none-any.whl", hash = "sha256:c4ea7e258a244858daf18474625e9cc0149b8ee354f37843415771a40c25ee06", size = 65566, upload-time = "2025-07-11T12:23:07.944Z" }, 1281 ] 1282 1283 [[package]] ··· 1383 { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, 1384 { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, 1385 { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, 1386 ] 1387 1388 [[package]] ··· 2280 sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" } 2281 wheels = [ 2282 { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, 2283 ] 2284 2285 [[package]]
··· 138 ] 139 140 [[package]] 141 + name = "asgiref" 142 + version = "3.11.1" 143 + source = { registry = "https://pypi.org/simple" } 144 + sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } 145 + wheels = [ 146 + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, 147 + ] 148 + 149 + [[package]] 150 name = "atproto" 151 version = "0.0.62.dev4" 152 source = { git = "https://github.com/MarshalX/atproto.git?rev=refs%2Fpull%2F605%2Fhead#1a2188371a25b248e0350826eda9f5e55d9c45bf" } ··· 190 { name = "atproto" }, 191 { name = "fastapi" }, 192 { name = "fastmcp" }, 193 + { name = "logfire", extra = ["fastapi"] }, 194 { name = "openai" }, 195 { name = "pydantic-ai" }, 196 { name = "pydantic-settings" }, 197 { name = "turbopuffer" }, 198 { name = "uvicorn" }, 199 ] ··· 212 { name = "atproto", git = "https://github.com/MarshalX/atproto.git?rev=refs%2Fpull%2F605%2Fhead" }, 213 { name = "fastapi" }, 214 { name = "fastmcp", specifier = ">=0.8.0" }, 215 + { name = "logfire", extras = ["fastapi"] }, 216 { name = "openai" }, 217 { name = "pydantic-ai" }, 218 { name = "pydantic-settings" }, 219 { name = "turbopuffer" }, 220 { name = "uvicorn" }, 221 ] ··· 504 ] 505 506 [[package]] 507 + name = "executing" 508 + version = "2.2.1" 509 + source = { registry = "https://pypi.org/simple" } 510 + sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } 511 + wheels = [ 512 + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, 513 + ] 514 + 515 + [[package]] 516 name = "fastapi" 517 version = "0.116.1" 518 source = { registry = "https://pypi.org/simple" } ··· 685 ] 686 687 [[package]] 688 + name = "googleapis-common-protos" 689 + version = "1.72.0" 690 + source = { registry = "https://pypi.org/simple" } 691 + dependencies = [ 692 + { name = "protobuf" }, 693 + ] 694 + sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } 695 + wheels = [ 696 + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, 697 + ] 698 + 699 + [[package]] 700 name = "griffe" 701 version = "1.7.3" 702 source = { registry = "https://pypi.org/simple" } ··· 1015 ] 1016 1017 [[package]] 1018 + name = "logfire" 1019 + version = "4.22.0" 1020 + source = { registry = "https://pypi.org/simple" } 1021 + dependencies = [ 1022 + { name = "executing" }, 1023 + { name = "opentelemetry-exporter-otlp-proto-http" }, 1024 + { name = "opentelemetry-instrumentation" }, 1025 + { name = "opentelemetry-sdk" }, 1026 + { name = "protobuf" }, 1027 + { name = "rich" }, 1028 + { name = "typing-extensions" }, 1029 + ] 1030 + sdist = { url = "https://files.pythonhosted.org/packages/b1/29/4386b331b8aecad429c043bbd1b7684cdb56ff9e11a5bd96845483dc0968/logfire-4.22.0.tar.gz", hash = "sha256:284b3b955c7515d4428dbc1e04784c3e652e62acf7597bd64a0aa9ecb6a7dedd", size = 654771, upload-time = "2026-02-04T12:17:57.635Z" } 1031 + wheels = [ 1032 + { url = "https://files.pythonhosted.org/packages/c7/ce/24a598c271d9631b09fb42f86f1e6d63b50647ecf0ce438d368a1e1af66b/logfire-4.22.0-py3-none-any.whl", hash = "sha256:9a0d20885613efe4bc9efcfa19a7943a6c642bc21339d926c51fcc74c0d083d6", size = 242637, upload-time = "2026-02-04T12:17:54.787Z" }, 1033 + ] 1034 + 1035 + [package.optional-dependencies] 1036 + fastapi = [ 1037 + { name = "opentelemetry-instrumentation-fastapi" }, 1038 + ] 1039 + 1040 + [[package]] 1041 name = "logfire-api" 1042 version = "3.25.0" 1043 source = { registry = "https://pypi.org/simple" } ··· 1322 1323 [[package]] 1324 name = "opentelemetry-api" 1325 + version = "1.39.1" 1326 source = { registry = "https://pypi.org/simple" } 1327 dependencies = [ 1328 { name = "importlib-metadata" }, 1329 { name = "typing-extensions" }, 1330 ] 1331 + sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } 1332 wheels = [ 1333 + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, 1334 + ] 1335 + 1336 + [[package]] 1337 + name = "opentelemetry-exporter-otlp-proto-common" 1338 + version = "1.39.1" 1339 + source = { registry = "https://pypi.org/simple" } 1340 + dependencies = [ 1341 + { name = "opentelemetry-proto" }, 1342 + ] 1343 + sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } 1344 + wheels = [ 1345 + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, 1346 + ] 1347 + 1348 + [[package]] 1349 + name = "opentelemetry-exporter-otlp-proto-http" 1350 + version = "1.39.1" 1351 + source = { registry = "https://pypi.org/simple" } 1352 + dependencies = [ 1353 + { name = "googleapis-common-protos" }, 1354 + { name = "opentelemetry-api" }, 1355 + { name = "opentelemetry-exporter-otlp-proto-common" }, 1356 + { name = "opentelemetry-proto" }, 1357 + { name = "opentelemetry-sdk" }, 1358 + { name = "requests" }, 1359 + { name = "typing-extensions" }, 1360 + ] 1361 + sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } 1362 + wheels = [ 1363 + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, 1364 + ] 1365 + 1366 + [[package]] 1367 + name = "opentelemetry-instrumentation" 1368 + version = "0.60b1" 1369 + source = { registry = "https://pypi.org/simple" } 1370 + dependencies = [ 1371 + { name = "opentelemetry-api" }, 1372 + { name = "opentelemetry-semantic-conventions" }, 1373 + { name = "packaging" }, 1374 + { name = "wrapt" }, 1375 + ] 1376 + sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } 1377 + wheels = [ 1378 + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, 1379 + ] 1380 + 1381 + [[package]] 1382 + name = "opentelemetry-instrumentation-asgi" 1383 + version = "0.60b1" 1384 + source = { registry = "https://pypi.org/simple" } 1385 + dependencies = [ 1386 + { name = "asgiref" }, 1387 + { name = "opentelemetry-api" }, 1388 + { name = "opentelemetry-instrumentation" }, 1389 + { name = "opentelemetry-semantic-conventions" }, 1390 + { name = "opentelemetry-util-http" }, 1391 + ] 1392 + sdist = { url = "https://files.pythonhosted.org/packages/77/db/851fa88db7441da82d50bd80f2de5ee55213782e25dc858e04d0c9961d60/opentelemetry_instrumentation_asgi-0.60b1.tar.gz", hash = "sha256:16bfbe595cd24cda309a957456d0fc2523f41bc7b076d1f2d7e98a1ad9876d6f", size = 26107, upload-time = "2025-12-11T13:36:47.015Z" } 1393 + wheels = [ 1394 + { url = "https://files.pythonhosted.org/packages/76/76/1fb94367cef64420d2171157a6b9509582873bd09a6afe08a78a8d1f59d9/opentelemetry_instrumentation_asgi-0.60b1-py3-none-any.whl", hash = "sha256:d48def2dbed10294c99cfcf41ebbd0c414d390a11773a41f472d20000fcddc25", size = 16933, upload-time = "2025-12-11T13:35:40.462Z" }, 1395 + ] 1396 + 1397 + [[package]] 1398 + name = "opentelemetry-instrumentation-fastapi" 1399 + version = "0.60b1" 1400 + source = { registry = "https://pypi.org/simple" } 1401 + dependencies = [ 1402 + { name = "opentelemetry-api" }, 1403 + { name = "opentelemetry-instrumentation" }, 1404 + { name = "opentelemetry-instrumentation-asgi" }, 1405 + { name = "opentelemetry-semantic-conventions" }, 1406 + { name = "opentelemetry-util-http" }, 1407 + ] 1408 + sdist = { url = "https://files.pythonhosted.org/packages/9c/e7/e7e5e50218cf488377209d85666b182fa2d4928bf52389411ceeee1b2b60/opentelemetry_instrumentation_fastapi-0.60b1.tar.gz", hash = "sha256:de608955f7ff8eecf35d056578346a5365015fd7d8623df9b1f08d1c74769c01", size = 24958, upload-time = "2025-12-11T13:36:59.35Z" } 1409 + wheels = [ 1410 + { url = "https://files.pythonhosted.org/packages/7d/cc/6e808328ba54662e50babdcab21138eae4250bc0fddf67d55526a615a2ca/opentelemetry_instrumentation_fastapi-0.60b1-py3-none-any.whl", hash = "sha256:af94b7a239ad1085fc3a820ecf069f67f579d7faf4c085aaa7bd9b64eafc8eaf", size = 13478, upload-time = "2025-12-11T13:36:00.811Z" }, 1411 + ] 1412 + 1413 + [[package]] 1414 + name = "opentelemetry-proto" 1415 + version = "1.39.1" 1416 + source = { registry = "https://pypi.org/simple" } 1417 + dependencies = [ 1418 + { name = "protobuf" }, 1419 + ] 1420 + sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } 1421 + wheels = [ 1422 + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, 1423 + ] 1424 + 1425 + [[package]] 1426 + name = "opentelemetry-sdk" 1427 + version = "1.39.1" 1428 + source = { registry = "https://pypi.org/simple" } 1429 + dependencies = [ 1430 + { name = "opentelemetry-api" }, 1431 + { name = "opentelemetry-semantic-conventions" }, 1432 + { name = "typing-extensions" }, 1433 + ] 1434 + sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } 1435 + wheels = [ 1436 + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, 1437 + ] 1438 + 1439 + [[package]] 1440 + name = "opentelemetry-semantic-conventions" 1441 + version = "0.60b1" 1442 + source = { registry = "https://pypi.org/simple" } 1443 + dependencies = [ 1444 + { name = "opentelemetry-api" }, 1445 + { name = "typing-extensions" }, 1446 + ] 1447 + sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } 1448 + wheels = [ 1449 + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, 1450 + ] 1451 + 1452 + [[package]] 1453 + name = "opentelemetry-util-http" 1454 + version = "0.60b1" 1455 + source = { registry = "https://pypi.org/simple" } 1456 + sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } 1457 + wheels = [ 1458 + { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, 1459 ] 1460 1461 [[package]] ··· 1561 { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, 1562 { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, 1563 { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, 1564 + ] 1565 + 1566 + [[package]] 1567 + name = "protobuf" 1568 + version = "6.33.5" 1569 + source = { registry = "https://pypi.org/simple" } 1570 + sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } 1571 + wheels = [ 1572 + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, 1573 + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, 1574 + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, 1575 + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, 1576 + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, 1577 + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, 1578 + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, 1579 ] 1580 1581 [[package]] ··· 2473 sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" } 2474 wheels = [ 2475 { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, 2476 + ] 2477 + 2478 + [[package]] 2479 + name = "wrapt" 2480 + version = "1.17.3" 2481 + source = { registry = "https://pypi.org/simple" } 2482 + sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } 2483 + wheels = [ 2484 + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, 2485 + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, 2486 + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, 2487 + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, 2488 + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, 2489 + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, 2490 + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, 2491 + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, 2492 + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, 2493 + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, 2494 + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, 2495 + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, 2496 + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, 2497 + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, 2498 + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, 2499 + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, 2500 + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, 2501 + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, 2502 + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, 2503 + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, 2504 + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, 2505 + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, 2506 + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, 2507 + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, 2508 + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, 2509 + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, 2510 + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, 2511 + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, 2512 + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, 2513 + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, 2514 + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, 2515 + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, 2516 + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, 2517 + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, 2518 + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, 2519 + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, 2520 + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, 2521 + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, 2522 + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, 2523 + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, 2524 + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, 2525 ] 2526 2527 [[package]]