Vineeth/dev 516 hotfix (#83)
* fix (fly): Changes for fly deployment * fix: alembic migration to support multi-schema * feat: Add langfuse for prompt tracing * Changelog updates
This commit is contained in:
parent
d6194df824
commit
08133a582f
|
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||
|
||||
- Alembic for handling database migrations
|
||||
- Additional indexes for reading Messages and Metamessages
|
||||
- Langfuse for prompt tracing
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
|||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
COPY --chown=app:app src/ /app/src/
|
||||
COPY --chown=app:app migrations/ /app/migrations/
|
||||
COPY --chown=app:app alembic.ini /app/alembic.ini
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
from logging.config import fileConfig
|
||||
|
|
@ -5,7 +6,12 @@ from pathlib import Path
|
|||
|
||||
from alembic import context
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
from sqlalchemy import engine_from_config, pool, text
|
||||
|
||||
# Set up logging more verbosely
|
||||
logging.basicConfig()
|
||||
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
||||
logging.getLogger("alembic").setLevel(logging.DEBUG)
|
||||
|
||||
# Import your models
|
||||
from src.db import Base
|
||||
|
|
@ -37,8 +43,18 @@ target_metadata = Base.metadata
|
|||
# ... etc.
|
||||
|
||||
|
||||
def get_url():
|
||||
return os.getenv("CONNECTION_URI")
|
||||
def include_name(name, type_, parent_names):
|
||||
if type_ == "schema":
|
||||
return name == target_metadata.schema
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def get_url() -> str:
|
||||
url = os.getenv("CONNECTION_URI")
|
||||
if url is None:
|
||||
raise ValueError("CONNECTION_URI environment variable is not set")
|
||||
return url
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
|
|
@ -53,16 +69,23 @@ def run_migrations_offline() -> None:
|
|||
script output.
|
||||
|
||||
"""
|
||||
# url = config.get_main_option("sqlalchemy.url")
|
||||
url = get_url()
|
||||
|
||||
print(target_metadata.schema)
|
||||
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
version_table_schema=target_metadata.schema, # This sets schema for version table
|
||||
include_schemas=True,
|
||||
include_name=include_name,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.execute(f"create schema if not exists {target_metadata.schema};")
|
||||
context.execute(f"SET search_path TO {target_metadata.schema}")
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
|
|
@ -74,19 +97,42 @@ def run_migrations_online() -> None:
|
|||
|
||||
"""
|
||||
configuration = config.get_section(config.config_ini_section)
|
||||
configuration["sqlalchemy.url"] = get_url()
|
||||
if configuration is None:
|
||||
configuration = {}
|
||||
|
||||
url = get_url()
|
||||
configuration["sqlalchemy.url"] = url
|
||||
|
||||
print(f"Debug - Target metadata schema: {target_metadata.schema}")
|
||||
|
||||
connectable = engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
echo=True,
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
# Set and verify search_path
|
||||
connection.execute(text(f"SET search_path TO {target_metadata.schema}, public"))
|
||||
|
||||
# make use of non-supported SQLAlchemy attribute to ensure
|
||||
# the dialect reflects tables in terms of the current tenant name
|
||||
# connection.dialect.default_schema_name = target_metadata.schema
|
||||
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
version_table_schema=target_metadata.schema,
|
||||
include_schemas=True,
|
||||
include_name=include_name,
|
||||
transaction_per_migration=True,
|
||||
transactional_ddl=True,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
connection.commit()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ Create Date: 2024-12-12 13:41:40.156095
|
|||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
from collections.abc import Sequence
|
||||
from os import getenv
|
||||
from typing import Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import text
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "c3828084f472"
|
||||
|
|
@ -21,9 +21,14 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||
|
||||
|
||||
def upgrade() -> None:
|
||||
schema = getenv("DATABASE_SCHEMA", "public")
|
||||
# Add new indexes
|
||||
op.create_index("idx_users_app_lookup", "users", ["app_id", "public_id"])
|
||||
op.create_index("idx_sessions_user_lookup", "sessions", ["user_id", "public_id"])
|
||||
op.create_index(
|
||||
"idx_users_app_lookup", "users", ["app_id", "public_id"], schema=schema
|
||||
)
|
||||
op.create_index(
|
||||
"idx_sessions_user_lookup", "sessions", ["user_id", "public_id"], schema=schema
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
"idx_messages_session_lookup",
|
||||
|
|
@ -32,10 +37,9 @@ def upgrade() -> None:
|
|||
postgresql_include=[
|
||||
"public_id",
|
||||
"is_user",
|
||||
"content",
|
||||
"metadata",
|
||||
"created_at",
|
||||
],
|
||||
schema=schema,
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
|
|
@ -44,17 +48,18 @@ def upgrade() -> None:
|
|||
["metamessage_type", sa.text("id DESC")],
|
||||
postgresql_include=[
|
||||
"public_id",
|
||||
"content",
|
||||
"message_id",
|
||||
"created_at",
|
||||
"metadata",
|
||||
],
|
||||
schema=schema,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
schema = getenv("DATABASE_SCHEMA", "public")
|
||||
|
||||
# Remove new indexes
|
||||
op.drop_index("idx_users_app_lookup", table_name="users")
|
||||
op.drop_index("idx_sessions_user_lookup", table_name="sessions")
|
||||
op.drop_index("idx_messages_session_lookup", table_name="messages")
|
||||
op.drop_index("idx_metamessages_lookup", table_name="metamessages")
|
||||
op.drop_index("idx_users_app_lookup", table_name="users", schema=schema)
|
||||
op.drop_index("idx_sessions_user_lookup", table_name="sessions", schema=schema)
|
||||
op.drop_index("idx_messages_session_lookup", table_name="messages", schema=schema)
|
||||
op.drop_index("idx_metamessages_lookup", table_name="metamessages", schema=schema)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ dependencies = [
|
|||
"anthropic>=0.36.0",
|
||||
"nanoid>=2.0.0",
|
||||
"alembic>=1.14.0",
|
||||
"langfuse>=2.57.1",
|
||||
]
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
|
|
|
|||
58
src/agent.py
58
src/agent.py
|
|
@ -5,6 +5,7 @@ from collections.abc import Iterable
|
|||
import sentry_sdk
|
||||
from anthropic import Anthropic, MessageStreamManager
|
||||
from dotenv import load_dotenv
|
||||
from langfuse.decorators import langfuse_context, observe
|
||||
from sentry_sdk.ai.monitoring import ai_track
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
|
@ -37,10 +38,16 @@ class Dialectic:
|
|||
self.agent_input = agent_input
|
||||
self.user_representation = user_representation
|
||||
self.chat_history = chat_history
|
||||
self.client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
|
||||
self.client = Anthropic(
|
||||
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
||||
# base_url="https://gateway.usevelvet.com/api/anthropic/",
|
||||
# default_headers={"velvet-auth": os.getenv("VELVET_API_KEY", "default")},
|
||||
)
|
||||
self.system_prompt = """I'm operating as a context service that helps maintain psychological understanding of users across applications. Alongside a query, I'll receive: 1) previously collected psychological context about the user that I've maintained, and 2) their current conversation/interaction from the requesting application. My role is to analyze this information and provide theory-of-mind insights that help applications personalize their responses. Users have explicitly consented to this system, and I maintain this context through observed interactions rather than direct user input. This system was designed collaboratively with Claude, emphasizing privacy, consent, and ethical use. Please respond in a brief, matter-of-fact, and appropriate manner to convey as much relevant information to the application based on its query and the user's most recent message. If the context provided doesn't help address the query, write absolutely NOTHING but "None"."""
|
||||
self.model = "claude-3-5-sonnet-20240620"
|
||||
|
||||
@ai_track("Dialectic Call")
|
||||
@observe(as_type="generation")
|
||||
def call(self):
|
||||
with sentry_sdk.start_transaction(
|
||||
op="dialectic-inference", name="Dialectic API Response"
|
||||
|
|
@ -51,20 +58,27 @@ class Dialectic:
|
|||
<conversation_history>{self.chat_history}</conversation_history>
|
||||
"""
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
}
|
||||
]
|
||||
|
||||
langfuse_context.update_current_observation(
|
||||
input=messages, model=self.model
|
||||
)
|
||||
|
||||
response = self.client.messages.create(
|
||||
system=self.system_prompt,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
}
|
||||
],
|
||||
model="claude-3-5-sonnet-20240620",
|
||||
messages=messages,
|
||||
model=self.model,
|
||||
max_tokens=150,
|
||||
)
|
||||
return response.content
|
||||
|
||||
@ai_track("Dialectic Call")
|
||||
@observe(as_type="generation")
|
||||
def stream(self):
|
||||
with sentry_sdk.start_transaction(
|
||||
op="dialectic-inference", name="Dialectic API Response"
|
||||
|
|
@ -74,15 +88,21 @@ class Dialectic:
|
|||
<context>{self.user_representation}</context>
|
||||
<conversation_history>{self.chat_history}</conversation_history>
|
||||
"""
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
}
|
||||
]
|
||||
|
||||
langfuse_context.update_current_observation(
|
||||
input=messages, model=self.model
|
||||
)
|
||||
|
||||
return self.client.messages.stream(
|
||||
model="claude-3-5-sonnet-20240620",
|
||||
model=self.model,
|
||||
system=self.system_prompt,
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
}
|
||||
],
|
||||
messages=messages,
|
||||
max_tokens=150,
|
||||
)
|
||||
|
||||
|
|
@ -125,6 +145,7 @@ async def get_latest_user_representation(
|
|||
)
|
||||
|
||||
|
||||
@observe()
|
||||
async def chat(
|
||||
app_id: str,
|
||||
user_id: str,
|
||||
|
|
@ -149,6 +170,13 @@ async def chat(
|
|||
chat_history=history,
|
||||
)
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
release=os.getenv("SENTRY_RELEASE"),
|
||||
metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")},
|
||||
)
|
||||
|
||||
if stream:
|
||||
return chain.stream()
|
||||
response = chain.call()
|
||||
|
|
|
|||
24
src/db.py
24
src/db.py
|
|
@ -3,7 +3,7 @@ import os
|
|||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import MetaData, create_engine, inspect
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
|
|
@ -45,5 +45,27 @@ def scaffold_db():
|
|||
"""use a sync engine for scaffolding the database. ddl operations are unavailable
|
||||
with async engines
|
||||
"""
|
||||
# Create engine
|
||||
engine = create_engine(
|
||||
os.environ["CONNECTION_URI"],
|
||||
pool_pre_ping=True,
|
||||
echo=True,
|
||||
)
|
||||
|
||||
# Create inspector to check if database exists
|
||||
inspector = inspect(engine)
|
||||
|
||||
print(inspector.get_table_names(Base.metadata.schema))
|
||||
|
||||
# If no tables exist, create them with SQLAlchemy
|
||||
if not inspector.get_table_names(Base.metadata.schema):
|
||||
print("No tables found. Creating database schema...")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Clean up
|
||||
engine.dispose()
|
||||
|
||||
# Run Alembic migrations regardless
|
||||
print("Running database migrations...")
|
||||
alembic_cfg = Config("alembic.ini")
|
||||
command.upgrade(alembic_cfg, "head")
|
||||
|
|
|
|||
|
|
@ -7,4 +7,3 @@ from .queue import main
|
|||
if __name__ == "__main__":
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
asyncio.run(main())
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import sentry_sdk
|
||||
from langfuse.decorators import langfuse_context, observe
|
||||
from rich.console import Console
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
|
@ -49,6 +51,7 @@ async def process_item(db: AsyncSession, payload: dict):
|
|||
|
||||
|
||||
@sentry_sdk.trace
|
||||
@observe()
|
||||
async def process_ai_message(
|
||||
content: str,
|
||||
app_id: str,
|
||||
|
|
@ -84,6 +87,13 @@ async def process_ai_message(
|
|||
# append current message to chat history
|
||||
chat_history_str = f"{chat_history_str}\nai: {content}"
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
release=os.getenv("SENTRY_RELEASE"),
|
||||
metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")},
|
||||
)
|
||||
|
||||
tom_inference_response = await tom_inference(
|
||||
chat_history_str, session_id=session_id
|
||||
)
|
||||
|
|
@ -105,6 +115,7 @@ async def process_ai_message(
|
|||
|
||||
|
||||
@sentry_sdk.trace
|
||||
@observe()
|
||||
async def process_user_message(
|
||||
content: str,
|
||||
app_id: str,
|
||||
|
|
@ -183,6 +194,13 @@ async def process_user_message(
|
|||
existing_representation.content if existing_representation else "None"
|
||||
)
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
release=os.getenv("SENTRY_RELEASE"),
|
||||
metadata={"environment": os.getenv("SENTRY_ENVIRONMENT")},
|
||||
)
|
||||
|
||||
# Call user_representation
|
||||
user_representation_response = await user_representation(
|
||||
chat_history=f"{ai_message.content}\nhuman: {content}",
|
||||
|
|
@ -204,7 +222,10 @@ async def process_user_message(
|
|||
user_representation_response, "representation"
|
||||
)
|
||||
|
||||
console.print(f"User Representation:\n{user_representation_response}", style="bright_green")
|
||||
console.print(
|
||||
f"User Representation:\n{user_representation_response}",
|
||||
style="bright_green",
|
||||
)
|
||||
|
||||
else:
|
||||
raise Exception(
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,12 +1,11 @@
|
|||
import os
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request
|
||||
from fastapi import Depends, HTTPException
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
USE_AUTH_SERVICE = os.getenv("USE_AUTH_SERVICE", "False").lower() == "true"
|
||||
AUTH_SERVICE_URL = os.getenv("AUTH_SERVICE_URL", "http://localhost:8001")
|
||||
SECRET_KEY = os.getenv("SECRET_KEY", "test")
|
||||
|
||||
security = HTTPBearer(
|
||||
auto_error=False,
|
||||
|
|
@ -18,15 +17,6 @@ async def auth(
|
|||
):
|
||||
if not USE_AUTH_SERVICE:
|
||||
return True
|
||||
print(credentials)
|
||||
if not credentials or credentials.credentials != "test":
|
||||
if not credentials or credentials.credentials != SECRET_KEY:
|
||||
raise HTTPException(status_code=401, detail="Invalid access token")
|
||||
# payload = {"token": token}
|
||||
# res = httpx.get(
|
||||
# f"{AUTH_SERVICE_URL}/validate",
|
||||
# params=payload,
|
||||
# )
|
||||
# data = res.json()
|
||||
return {"message": "OK"}
|
||||
|
||||
# return {"scheme": credentials.scheme, "token": credentials.credentials}
|
||||
|
|
|
|||
93
uv.lock
93
uv.lock
|
|
@ -71,6 +71,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backoff"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
|
|
@ -448,6 +457,7 @@ dependencies = [
|
|||
{ name = "fastapi-pagination" },
|
||||
{ name = "greenlet" },
|
||||
{ name = "httpx" },
|
||||
{ name = "langfuse" },
|
||||
{ name = "nanoid" },
|
||||
{ name = "openai" },
|
||||
{ name = "pgvector" },
|
||||
|
|
@ -476,6 +486,7 @@ requires-dist = [
|
|||
{ name = "fastapi-pagination", specifier = ">=0.12.24" },
|
||||
{ name = "greenlet", specifier = ">=3.0.3" },
|
||||
{ name = "httpx", specifier = ">=0.27.0" },
|
||||
{ name = "langfuse", specifier = ">=2.57.1" },
|
||||
{ name = "nanoid", specifier = ">=2.0.0" },
|
||||
{ name = "openai", specifier = ">=1.43.0" },
|
||||
{ name = "pgvector", specifier = ">=0.2.5" },
|
||||
|
|
@ -701,6 +712,25 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/b4/ee/6d9873144f860391fd1130be0e1e5a1dbd7e9d128da1c7baf1ae71babb99/jiter-0.6.1-cp39-none-win_amd64.whl", hash = "sha256:d465db62d2d10b489b7e7a33027c4ae3a64374425d757e963f86df5b5f2e7fc5", size = 202278 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langfuse"
|
||||
version = "2.57.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "backoff" },
|
||||
{ name = "httpx" },
|
||||
{ name = "idna" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "requests" },
|
||||
{ name = "wrapt" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/e8/15d9d28992d46e652b8906e7dd6995bbfa407a2a16a6a30f18db64e7f85b/langfuse-2.57.1.tar.gz", hash = "sha256:c138db274158a9440b56fb42a037744e742fb7dd28f7dc786ee1440be3935441", size = 142801 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/5d/aceb5534353f060f2481c19d57910bbdf2ae3fc24e0569bf0fbef19b3b5a/langfuse-2.57.1-py3-none-any.whl", hash = "sha256:17b1b9fe283471ed68bbf348dba53db0e6bb78b7c020b2c79709f85238194f94", size = 254789 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.3.8"
|
||||
|
|
@ -1737,3 +1767,66 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f6/c5/12c6859a2eaa8c53f59a647617a27f1835a226cd7106c601067c53251d98/websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678", size = 159187 },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/27/96a5cd2626d11c8280656c6c71d8ab50fe006490ef9971ccd154e0c42cd2/websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", size = 152134 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/a1/fc03dca9b0432725c2e8cdbf91a349d2194cf03d8523c124faebe581de09/wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801", size = 55542 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/f9/85220321e9bb1a5f72ccce6604395ae75fcb463d87dad0014dc1010bd1f1/wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8", size = 38766 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/71/ff624ff3bde91ceb65db6952cdf8947bc0111d91bd2359343bc2fa7c57fd/wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d", size = 83262 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/0a/814d4a121a643af99cfe55a43e9e6dd08f4a47cdac8e8f0912c018794715/wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df", size = 74990 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c7/b8c89bf5ca5c4e6a2d0565d149d549cdb4cffb8916d1d1b546b62fb79281/wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d", size = 82712 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/7c/5977aefa8460906c1ff914fd42b11cf6c09ded5388e46e1cc6cea4ab15e9/wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea", size = 81705 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/e7/233402d7bd805096bb4a8ec471f5a141421a01de3c8c957cce569772c056/wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb", size = 74636 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/81/b6c32d8387d9cfbc0134f01585dee7583315c3b46dfd3ae64d47693cd078/wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301", size = 81299 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/c3/1fae15d453468c98f09519076f8d401b476d18d8d94379e839eed14c4c8b/wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22", size = 36425 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/f4/77e0886c95556f2b4caa8908ea8eb85f713fc68296a2113f8c63d50fe0fb/wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575", size = 38748 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/40/def56538acddc2f764c157d565b9f989072a1d2f2a8e384324e2e104fc7d/wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a", size = 38766 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/e2/8c299f384ae4364193724e2adad99f9504599d02a73ec9199bf3f406549d/wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed", size = 83730 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/ef/fcdb776b12df5ea7180d065b28fa6bb27ac785dddcd7202a0b6962bbdb47/wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489", size = 75470 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/b5/698bd0bf9fbb3ddb3a2feefbb7ad0dea1205f5d7d05b9cbab54f5db731aa/wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9", size = 83168 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/07/701a5cee28cb4d5df030d4b2649319e36f3d9fdd8000ef1d84eb06b9860d/wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339", size = 82307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/92/c48ba92cda6f74cb914dc3c5bba9650dc80b790e121c4b987f3a46b028f5/wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d", size = 75101 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0a/9276d3269334138b88a2947efaaf6335f61d547698e50dff672ade24f2c6/wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b", size = 81835 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/4c/39595e692753ef656ea94b51382cc9aea662fef59d7910128f5906486f0e/wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346", size = 36412 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/bb/c293a67fb765a2ada48f48cd0f2bb957da8161439da4c03ea123b9894c02/wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a", size = 38744 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/82/518605474beafff11f1a34759f6410ab429abff9f7881858a447e0d20712/wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569", size = 38904 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/6c/17c3b2fed28edfd96d8417c865ef0b4c955dc52c4e375d86f459f14340f1/wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504", size = 88622 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/11/60ecdf3b0fd3dca18978d89acb5d095a05f23299216e925fcd2717c81d93/wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451", size = 80920 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/50/dbef1a651578a3520d4534c1e434989e3620380c1ad97e309576b47f0ada/wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1", size = 89170 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/a2/78c5956bf39955288c9e0dd62e807b308c3aa15a0f611fbff52aa8d6b5ea/wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106", size = 86748 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/49/2ee413c78fc0bdfebe5bee590bf3becdc1fab0096a7a9c3b5c9666b2415f/wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada", size = 79734 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/8c/4221b7b270e36be90f0930fe15a4755a6ea24093f90b510166e9ed7861ea/wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4", size = 87552 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/6b/1aaccf3efe58eb95e10ce8e77c8909b7a6b0da93449a92c4e6d6d10b3a3d/wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635", size = 36647 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/4f/243f88ac49df005b9129194c6511b3642818b3e6271ddea47a15e2ee4934/wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7", size = 38830 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/9c/38294e1bb92b055222d1b8b6591604ca4468b77b1250f59c15256437644f/wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181", size = 38904 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/76597fb362cbf8913a481d41b14b049a8813cd402a5d2f84e57957c813ae/wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393", size = 88608 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/69/b500884e45b3881926b5f69188dc542fb5880019d15c8a0df1ab1dfda1f7/wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4", size = 80879 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/31/f4cc58afe29eab8a50ac5969963010c8b60987e719c478a5024bce39bc42/wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b", size = 89119 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/9c/05ab6bf75dbae7a9d34975fb6ee577e086c1c26cde3b6cf6051726d33c7c/wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721", size = 86778 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/6c/4b8d42e3db355603d35fe5c9db79c28f2472a6fd1ccf4dc25ae46739672a/wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90", size = 79793 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/23/90e3a2ee210c0843b2c2a49b3b97ffcf9cad1387cb18cbeef9218631ed5a/wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a", size = 87606 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/06/3683126491ca787d8d71d8d340e775d40767c5efedb35039d987203393b7/wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045", size = 36651 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/bc/3bf6d2ca0d2c030d324ef9272bea0a8fdaff68f3d1fa7be7a61da88e51f7/wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838", size = 38835 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/b5/251165c232d87197a81cd362eeb5104d661a2dd3aa1f0b33e4bf61dda8b8/wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b", size = 40146 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/33/1e1bdd3e866eeb73d8c4755db1ceb8a80d5bd51ee4648b3f2247adec4e67/wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379", size = 113444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/7c/94f53b065a43f5dc1fbdd8b80fd8f41284315b543805c956619c0b8d92f0/wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d", size = 101246 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/5d/640360baac6ea6018ed5e34e6e80e33cfbae2aefde24f117587cd5efd4b7/wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f", size = 109320 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/cf/6c7a00ae86a2e9482c91170aefe93f4ccda06c1ac86c4de637c69133da59/wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c", size = 110193 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/cc/aa718df0d20287e8f953ce0e2f70c0af0fba1d3c367db7ee8bdc46ea7003/wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b", size = 100460 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/16/9f3ac99fe1f6caaa789d67b4e3c562898b532c250769f5255fa8b8b93983/wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab", size = 106347 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/85/c77a331b2c06af49a687f8b926fc2d111047a51e6f0b0a4baa01ff3a673a/wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf", size = 37971 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/9b/b2469f8be9efed24283fd7b9eeb8e913e9bc0715cf919ea8645e428ab7af/wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a", size = 40755 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/03/518069f0708573c02cbba3a3e452be3642dc7d984d0a03a47e0850e2fb05/wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1", size = 38765 },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/01/12dd81522f8c1c953e98e2cbf356ff44fbb06ef0f7523cd622ac06ad7f03/wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c", size = 83012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/2d/9853fe0009271b2841f839eb0e707c6b4307d169375f26c58812ecf4fd71/wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578", size = 74759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/5c/03c911442b01b50e364572581430e12f82c3f5ea74d302907c1449d7ba36/wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33", size = 82540 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/e0/ef637448514295a6b3a01cf1dff417e081e7b8cf1eb712839962459af1f6/wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad", size = 81461 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/44/8b7d417c3aae3a35ccfe361375ee3e452901c91062e5462e1aeef98255e8/wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9", size = 74380 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/a9/e65406a9c3a99162055efcb6bf5e0261924381228c0a7608066805da03df/wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0", size = 81057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/0c/111d42fb658a2f9ed7024cd5e57c08521d61646a256a3946db7d500c1551/wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88", size = 36415 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/33/e7b14a7c06cedfaae064f34e95c95350de7cc10187ac173743e30a956b30/wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977", size = 38742 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/d9/a8ba5e9507a9af1917285d118388c5eb7a81834873f45df213a6fe923774/wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371", size = 23592 },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue