diff --git a/autogpt_platform/docker-compose.platform.yml b/autogpt_platform/docker-compose.platform.yml index e013616db..a09fed4c8 100644 --- a/autogpt_platform/docker-compose.platform.yml +++ b/autogpt_platform/docker-compose.platform.yml @@ -144,6 +144,51 @@ services: networks: - app-network + market: + build: + context: ../ + dockerfile: autogpt_platform/market/Dockerfile + develop: + watch: + - path: ./ + target: autogpt_platform/market/ + action: rebuild + depends_on: + db: + condition: service_healthy + market-migrations: + condition: service_completed_successfully + environment: + - SUPABASE_URL=http://kong:8000 + - SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long + - SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE + - DATABASE_URL=postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=market + - BACKEND_CORS_ALLOW_ORIGINS="http://localhost:3000,http://127.0.0.1:3000" + ports: + - "8015:8015" + networks: + - app-network + + market-migrations: + build: + context: ../ + dockerfile: autogpt_platform/market/Dockerfile + command: ["sh", "-c", "poetry run prisma migrate deploy"] + develop: + watch: + - path: ./ + target: autogpt_platform/market/ + action: rebuild + depends_on: + db: + condition: service_healthy + environment: + - SUPABASE_URL=http://kong:8000 + - SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long + - SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE + - DATABASE_URL=postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=market + networks: + - app-network # frontend: # build: # context: ../ diff --git a/autogpt_platform/docker-compose.yml b/autogpt_platform/docker-compose.yml index 9166e0e32..d9cafccf0 100644 --- a/autogpt_platform/docker-compose.yml +++ b/autogpt_platform/docker-compose.yml @@ -51,6 +51,18 @@ services: file: ./docker-compose.platform.yml service: websocket_server + market: + <<: *agpt-services + extends: + file: ./docker-compose.platform.yml + service: market + + market-migrations: + <<: *agpt-services + extends: + file: ./docker-compose.platform.yml + service: market-migrations + # frontend: # <<: *agpt-services # extends: diff --git a/autogpt_platform/market/.env.example b/autogpt_platform/market/.env.example new file mode 100644 index 000000000..b3b1cb8a4 --- /dev/null +++ b/autogpt_platform/market/.env.example @@ -0,0 +1,12 @@ +DB_USER=postgres +DB_PASS=your-super-secret-and-long-postgres-password +DB_NAME=postgres +DB_PORT=5432 +DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/${DB_NAME}?connect_timeout=60&schema=market" +SENTRY_DSN=https://11d0640fef35640e0eb9f022eb7d7626@o4505260022104064.ingest.us.sentry.io/4507890252447744 + +ENABLE_AUTH=true +SUPABASE_JWT_SECRET=our-super-secret-jwt-token-with-at-least-32-characters-long +BACKEND_CORS_ALLOW_ORIGINS="http://localhost:3000,http://127.0.0.1:3000" + +APP_ENV=local diff --git a/autogpt_platform/market/.gitignore b/autogpt_platform/market/.gitignore new file mode 100644 index 000000000..7fd0341ba --- /dev/null +++ b/autogpt_platform/market/.gitignore @@ -0,0 +1,6 @@ +database.db +database.db-journal +build/ +config.json +secrets/* +!secrets/.gitkeep \ No newline at end of file diff --git a/autogpt_platform/market/Dockerfile b/autogpt_platform/market/Dockerfile new file mode 100644 index 000000000..dbd12565d --- /dev/null +++ b/autogpt_platform/market/Dockerfile @@ -0,0 +1,72 @@ +FROM python:3.11.10-slim-bookworm AS builder + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +WORKDIR /app + +RUN echo 'Acquire::http::Pipeline-Depth 0;\nAcquire::http::No-Cache true;\nAcquire::BrokenProxy true;\n' > /etc/apt/apt.conf.d/99fixbadproxy + +RUN apt-get update --allow-releaseinfo-change --fix-missing + +# Install build dependencies +RUN apt-get install -y build-essential +RUN apt-get install -y libpq5 +RUN apt-get install -y libz-dev +RUN apt-get install -y libssl-dev + +ENV POETRY_VERSION=1.8.3 \ + POETRY_HOME="/opt/poetry" \ + POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_CREATE=false +ENV PATH="$POETRY_HOME/bin:$PATH" + +# Upgrade pip and setuptools to fix security vulnerabilities +RUN pip3 install --upgrade pip setuptools + +RUN pip3 install poetry + +# Copy and install dependencies +COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs +COPY autogpt_platform/market/poetry.lock autogpt_platform/market/pyproject.toml /app/autogpt_platform/market/ +WORKDIR /app/autogpt_platform/market +RUN poetry config virtualenvs.create false \ + && poetry install --no-interaction --no-ansi + +# Generate Prisma client +COPY autogpt_platform/market /app/autogpt_platform/market +RUN poetry config virtualenvs.create false \ + && poetry run prisma generate + +FROM python:3.11.10-slim-bookworm AS server_dependencies + +WORKDIR /app + +# Upgrade pip and setuptools to fix security vulnerabilities +RUN pip3 install --upgrade pip setuptools + +# Copy only necessary files from builder +COPY --from=builder /app /app +COPY --from=builder /usr/local/lib/python3.11 /usr/local/lib/python3.11 +COPY --from=builder /usr/local/bin /usr/local/bin +# Copy Prisma binaries +COPY --from=builder /root/.cache/prisma-python/binaries /root/.cache/prisma-python/binaries + +ENV PATH="/app/.venv/bin:$PATH" + +RUN mkdir -p /app/autogpt_platform/autogpt_libs +RUN mkdir -p /app/autogpt_platform/market + +COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs + +COPY autogpt_platform/market /app/autogpt_platform/market + +WORKDIR /app/autogpt_platform/market + +FROM server_dependencies AS server + +ENV DATABASE_URL="" +ENV PORT=8015 + +CMD ["poetry", "run", "app"] diff --git a/autogpt_platform/market/README.md b/autogpt_platform/market/README.md new file mode 100644 index 000000000..a799f362b --- /dev/null +++ b/autogpt_platform/market/README.md @@ -0,0 +1,37 @@ +# AutoGPT Agent Marketplace + +## Overview + +AutoGPT Agent Marketplace is an open-source platform for autonomous AI agents. This project aims to create a user-friendly, accessible marketplace where users can discover, utilize, and contribute to a diverse ecosystem of AI solutions. + +## Vision + +Our vision is to empower users with customizable and free AI agents, fostering an open-source community that drives innovation in AI automation across various industries. + +## Key Features + +- Agent Discovery and Search +- Agent Listings with Detailed Information +- User Profiles +- Data Protection and Compliance + +## Getting Started + +To get started with the AutoGPT Agent Marketplace, follow these steps: + +- Copy `.env.example` to `.env` and fill in the required environment variables +- Run `poetry run setup` +- Run `poetry run populate` +- Run `poetry run app` + +## Poetry Run Commands + +This section outlines the available command line scripts for this project, configured using Poetry. You can execute these scripts directly using Poetry. Each command performs a specific operation as described below: + +- `poetry run format`: Runs the formatting script to ensure code consistency. +- `poetry run lint`: Executes the linting script to identify and fix potential code issues. +- `poetry run app`: Starts the main application. +- `poetry run setup`: Runs the setup script to configure the database. +- `poetry run populate`: Populates the database with initial data using the specified script. + +To run any of these commands, ensure Poetry is installed on your system and execute the commands from the project's root directory. diff --git a/autogpt_platform/market/docker-compose.yml b/autogpt_platform/market/docker-compose.yml new file mode 100644 index 000000000..3eaa40892 --- /dev/null +++ b/autogpt_platform/market/docker-compose.yml @@ -0,0 +1,16 @@ +version: "3" +services: + postgres: + image: ankane/pgvector:latest + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASS} + POSTGRES_DB: ${DB_NAME} + PGUSER: ${DB_USER} + healthcheck: + test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB + interval: 10s + timeout: 5s + retries: 5 + ports: + - "${DB_PORT}:5432" diff --git a/autogpt_platform/market/market/__init__.py b/autogpt_platform/market/market/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/autogpt_platform/market/market/app.py b/autogpt_platform/market/market/app.py new file mode 100644 index 000000000..63736acd3 --- /dev/null +++ b/autogpt_platform/market/market/app.py @@ -0,0 +1,97 @@ +import contextlib +import logging.config +import os + +import dotenv +import fastapi +import fastapi.middleware.cors +import fastapi.middleware.gzip +import prisma +import prometheus_fastapi_instrumentator +import sentry_sdk +import sentry_sdk.integrations.asyncio +import sentry_sdk.integrations.fastapi +import sentry_sdk.integrations.starlette + +import market.config +import market.routes.admin +import market.routes.agents +import market.routes.analytics +import market.routes.search +import market.routes.submissions + +dotenv.load_dotenv() + +logging.config.dictConfig(market.config.LogConfig().model_dump()) + +if os.environ.get("SENTRY_DSN"): + sentry_sdk.init( + dsn=os.environ.get("SENTRY_DSN"), + traces_sample_rate=1.0, + profiles_sample_rate=1.0, + enable_tracing=True, + environment=os.environ.get("RUN_ENV", default="CLOUD").lower(), + integrations=[ + sentry_sdk.integrations.starlette.StarletteIntegration( + transaction_style="url" + ), + sentry_sdk.integrations.fastapi.FastApiIntegration(transaction_style="url"), + sentry_sdk.integrations.asyncio.AsyncioIntegration(), + ], + ) + +db_client = prisma.Prisma(auto_register=True) + + +@contextlib.asynccontextmanager +async def lifespan(app: fastapi.FastAPI): + await db_client.connect() + yield + await db_client.disconnect() + + +docs_url = "/docs" +app = fastapi.FastAPI( + title="Marketplace API", + description="AutoGPT Marketplace API is a service that allows users to share AI agents.", + summary="Maketplace API", + version="0.1", + lifespan=lifespan, + root_path="/api/v1/market", + docs_url=docs_url, +) + +app.add_middleware(fastapi.middleware.gzip.GZipMiddleware, minimum_size=1000) +app.add_middleware( + middleware_class=fastapi.middleware.cors.CORSMiddleware, + allow_origins=os.environ.get( + "BACKEND_CORS_ALLOW_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000" + ).split(","), + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +app.include_router(market.routes.agents.router, tags=["agents"]) +app.include_router(market.routes.search.router, tags=["search"]) +app.include_router(market.routes.submissions.router, tags=["submissions"]) +app.include_router(market.routes.admin.router, prefix="/admin", tags=["admin"]) +app.include_router( + market.routes.analytics.router, prefix="/analytics", tags=["analytics"] +) + + +@app.get("/health") +def health(): + return fastapi.responses.HTMLResponse( + content="

Marketplace API

", status_code=200 + ) + + +@app.get("/") +def default(): + return fastapi.responses.HTMLResponse( + content="

Marketplace API

", status_code=200 + ) + + +prometheus_fastapi_instrumentator.Instrumentator().instrument(app).expose(app) diff --git a/autogpt_platform/market/market/config.py b/autogpt_platform/market/market/config.py new file mode 100644 index 000000000..46a31b462 --- /dev/null +++ b/autogpt_platform/market/market/config.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel + + +class LogConfig(BaseModel): + """Logging configuration to be set for the server""" + + LOGGER_NAME: str = "marketplace" + LOG_FORMAT: str = "%(levelprefix)s | %(asctime)s | %(message)s" + LOG_LEVEL: str = "DEBUG" + + # Logging config + version: int = 1 + disable_existing_loggers: bool = False + formatters: dict = { + "default": { + "()": "uvicorn.logging.DefaultFormatter", + "fmt": LOG_FORMAT, + "datefmt": "%Y-%m-%d %H:%M:%S", + }, + } + handlers: dict = { + "default": { + "formatter": "default", + "class": "logging.StreamHandler", + "stream": "ext://sys.stderr", + }, + } + loggers: dict = { + LOGGER_NAME: {"handlers": ["default"], "level": LOG_LEVEL}, + } diff --git a/autogpt_platform/market/market/db.py b/autogpt_platform/market/market/db.py new file mode 100644 index 000000000..6b4418bd8 --- /dev/null +++ b/autogpt_platform/market/market/db.py @@ -0,0 +1,725 @@ +import datetime +import typing + +import fuzzywuzzy.fuzz +import prisma.enums +import prisma.errors +import prisma.models +import prisma.types +import pydantic + +import market.model +import market.utils.extension_types + + +class AgentQueryError(Exception): + """Custom exception for agent query errors""" + + pass + + +class TopAgentsDBResponse(pydantic.BaseModel): + """ + Represents a response containing a list of top agents. + + Attributes: + analytics (list[AgentResponse]): The list of top agents. + total_count (int): The total count of agents. + page (int): The current page number. + page_size (int): The number of agents per page. + total_pages (int): The total number of pages. + """ + + analytics: list[prisma.models.AnalyticsTracker] + total_count: int + page: int + page_size: int + total_pages: int + + +class FeaturedAgentResponse(pydantic.BaseModel): + """ + Represents a response containing a list of featured agents. + + Attributes: + featured_agents (list[FeaturedAgent]): The list of featured agents. + total_count (int): The total count of featured agents. + page (int): The current page number. + page_size (int): The number of agents per page. + total_pages (int): The total number of pages. + """ + + featured_agents: list[prisma.models.FeaturedAgent] + total_count: int + page: int + page_size: int + total_pages: int + + +async def delete_agent(agent_id: str) -> prisma.models.Agents | None: + """ + Delete an agent from the database. + + Args: + agent_id (str): The ID of the agent to delete. + + Returns: + prisma.models.Agents | None: The deleted agent if found, None otherwise. + + Raises: + AgentQueryError: If there is an error deleting the agent from the database. + """ + try: + deleted_agent = await prisma.models.Agents.prisma().delete( + where={"id": agent_id} + ) + return deleted_agent + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def create_agent_entry( + name: str, + description: str, + author: str, + keywords: typing.List[str], + categories: typing.List[str], + graph: prisma.Json, + submission_state: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.PENDING, +): + """ + Create a new agent entry in the database. + + Args: + name (str): The name of the agent. + description (str): The description of the agent. + author (str): The author of the agent. + keywords (List[str]): The keywords associated with the agent. + categories (List[str]): The categories associated with the agent. + graph (dict): The graph data of the agent. + + Returns: + dict: The newly created agent entry. + + Raises: + AgentQueryError: If there is an error creating the agent entry. + """ + try: + agent = await prisma.models.Agents.prisma().create( + data={ + "name": name, + "description": description, + "author": author, + "keywords": keywords, + "categories": categories, + "graph": graph, + "AnalyticsTracker": {"create": {"downloads": 0, "views": 0}}, + "submissionStatus": submission_state, + } + ) + + return agent + + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def update_agent_entry( + agent_id: str, + version: int, + submission_state: prisma.enums.SubmissionStatus, + comments: str | None = None, +) -> prisma.models.Agents | None: + """ + Update an existing agent entry in the database. + + Args: + agent_id (str): The ID of the agent. + version (int): The version of the agent. + submission_state (prisma.enums.SubmissionStatus): The submission state of the agent. + """ + + try: + agent = await prisma.models.Agents.prisma().update( + where={"id": agent_id}, + data={ + "version": version, + "submissionStatus": submission_state, + "submissionReviewDate": datetime.datetime.now(datetime.timezone.utc), + "submissionReviewComments": comments, + }, + ) + + return agent + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Agent Update Failed Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_agents( + page: int = 1, + page_size: int = 10, + name: str | None = None, + keyword: str | None = None, + category: str | None = None, + description: str | None = None, + description_threshold: int = 60, + submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED, + sort_by: str = "createdAt", + sort_order: typing.Literal["desc"] | typing.Literal["asc"] = "desc", +): + """ + Retrieve a list of agents from the database based on the provided filters and pagination parameters. + + Args: + page (int, optional): The page number to retrieve. Defaults to 1. + page_size (int, optional): The number of agents per page. Defaults to 10. + name (str, optional): Filter agents by name. Defaults to None. + keyword (str, optional): Filter agents by keyword. Defaults to None. + category (str, optional): Filter agents by category. Defaults to None. + description (str, optional): Filter agents by description. Defaults to None. + description_threshold (int, optional): The minimum fuzzy search threshold for the description. Defaults to 60. + sort_by (str, optional): The field to sort the agents by. Defaults to "createdAt". + sort_order (str, optional): The sort order ("asc" or "desc"). Defaults to "desc". + + Returns: + dict: A dictionary containing the list of agents, total count, current page number, page size, and total number of pages. + """ + try: + # Define the base query + query = {} + + # Add optional filters + if name: + query["name"] = {"contains": name, "mode": "insensitive"} + if keyword: + query["keywords"] = {"has": keyword} + if category: + query["categories"] = {"has": category} + + query["submissionStatus"] = submission_status + + # Define sorting + order = {sort_by: sort_order} + + # Calculate pagination + skip = (page - 1) * page_size + + # Execute the query + try: + agents = await prisma.models.Agents.prisma().find_many( + where=query, # type: ignore + order=order, # type: ignore + skip=skip, + take=page_size, + ) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + + # Apply fuzzy search on description if provided + if description: + try: + filtered_agents = [] + for agent in agents: + if ( + agent.description + and fuzzywuzzy.fuzz.partial_ratio( + description.lower(), agent.description.lower() + ) + >= description_threshold + ): + filtered_agents.append(agent) + agents = filtered_agents + except AttributeError as e: + raise AgentQueryError(f"Error during fuzzy search: {str(e)}") + + # Get total count for pagination info + total_count = len(agents) + + return { + "agents": agents, + "total_count": total_count, + "page": page, + "page_size": page_size, + "total_pages": (total_count + page_size - 1) // page_size, + } + + except AgentQueryError as e: + # Log the error or handle it as needed + raise e + except ValueError as e: + raise AgentQueryError(f"Invalid input parameter: {str(e)}") + except Exception as e: + # Catch any other unexpected exceptions + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_agent_details(agent_id: str, version: int | None = None): + """ + Retrieve agent details from the database. + + Args: + agent_id (str): The ID of the agent. + version (int | None, optional): The version of the agent. Defaults to None. + + Returns: + dict: The agent details. + + Raises: + AgentQueryError: If the agent is not found or if there is an error querying the database. + """ + try: + query = {"id": agent_id} + if version is not None: + query["version"] = version # type: ignore + + agent = await prisma.models.Agents.prisma().find_first(where=query) # type: ignore + + if not agent: + raise AgentQueryError("Agent not found") + + return agent + + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def search_db( + query: str, + page: int = 1, + page_size: int = 10, + categories: typing.List[str] | None = None, + description_threshold: int = 60, + sort_by: str = "rank", + sort_order: typing.Literal["desc"] | typing.Literal["asc"] = "desc", + submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED, +) -> market.model.ListResponse[market.utils.extension_types.AgentsWithRank]: + """Perform a search for agents based on the provided query string. + + Args: + query (str): the search string + page (int, optional): page for searching. Defaults to 1. + page_size (int, optional): the number of results to return. Defaults to 10. + categories (List[str] | None, optional): list of category filters. Defaults to None. + description_threshold (int, optional): number of characters to return. Defaults to 60. + sort_by (str, optional): sort by option. Defaults to "rank". + sort_order ("asc" | "desc", optional): the sort order. Defaults to "desc". + + Raises: + AgentQueryError: Raises an error if the query fails. + AgentQueryError: Raises if an unexpected error occurs. + + Returns: + List[AgentsWithRank]: List of agents matching the search criteria. + """ + try: + offset = (page - 1) * page_size + + category_filter = "1=1" + if categories: + category_conditions = [f"'{cat}' = ANY(categories)" for cat in categories] + category_filter = "AND (" + " OR ".join(category_conditions) + ")" + + # Construct the ORDER BY clause based on the sort_by parameter + if sort_by in ["createdAt", "updatedAt"]: + order_by_clause = f'"{sort_by}" {sort_order.upper()}, rank DESC' + elif sort_by == "name": + order_by_clause = f"name {sort_order.upper()}, rank DESC" + else: + order_by_clause = 'rank DESC, "createdAt" DESC' + + submission_status_filter = f""""submissionStatus" = '{submission_status}'""" + + sql_query = f""" + WITH query AS ( + SELECT to_tsquery(string_agg(lexeme || ':*', ' & ' ORDER BY positions)) AS q + FROM unnest(to_tsvector('{query}')) + ) + SELECT + id, + "createdAt", + "updatedAt", + version, + name, + LEFT(description, {description_threshold}) AS description, + author, + keywords, + categories, + graph, + "submissionStatus", + "submissionDate", + CASE + WHEN query.q::text = '' THEN 1.0 + ELSE COALESCE(ts_rank(CAST(search AS tsvector), query.q), 0.0) + END AS rank + FROM market."Agents", query + WHERE + (query.q::text = '' OR search @@ query.q) + AND {category_filter} + AND {submission_status_filter} + ORDER BY {order_by_clause} + LIMIT {page_size} + OFFSET {offset}; + """ + + results = await prisma.client.get_client().query_raw( + query=sql_query, + model=market.utils.extension_types.AgentsWithRank, + ) + + class CountResponse(pydantic.BaseModel): + count: int + + count_query = f""" + WITH query AS ( + SELECT to_tsquery(string_agg(lexeme || ':*', ' & ' ORDER BY positions)) AS q + FROM unnest(to_tsvector('{query}')) + ) + SELECT COUNT(*) + FROM market."Agents", query + WHERE (search @@ query.q OR query.q = '') AND {category_filter} AND {submission_status_filter}; + """ + + total_count = await prisma.client.get_client().query_first( + query=count_query, + model=CountResponse, + ) + total_count = total_count.count if total_count else 0 + + return market.model.ListResponse( + items=results, + total_count=total_count, + page=page, + page_size=page_size, + total_pages=(total_count + page_size - 1) // page_size, + ) + + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_top_agents_by_downloads( + page: int = 1, + page_size: int = 10, + submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED, +) -> market.model.ListResponse[prisma.models.AnalyticsTracker]: + """Retrieve the top agents by download count. + + Args: + page (int, optional): The page number. Defaults to 1. + page_size (int, optional): The number of agents per page. Defaults to 10. + + Returns: + dict: A dictionary containing the list of agents, total count, current page number, page size, and total number of pages. + """ + try: + # Calculate pagination + skip = (page - 1) * page_size + + # Execute the query + try: + # Agents with no downloads will not be included in the results... is this the desired behavior? + analytics = await prisma.models.AnalyticsTracker.prisma().find_many( + include={"agent": True}, + order={"downloads": "desc"}, + where={"agent": {"is": {"submissionStatus": submission_status}}}, + skip=skip, + take=page_size, + ) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + + try: + total_count = await prisma.models.AnalyticsTracker.prisma().count( + where={"agent": {"is": {"submissionStatus": submission_status}}}, + ) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + + return market.model.ListResponse( + items=analytics, + total_count=total_count, + page=page, + page_size=page_size, + total_pages=(total_count + page_size - 1) // page_size, + ) + + except AgentQueryError as e: + # Log the error or handle it as needed + raise e from e + except ValueError as e: + raise AgentQueryError(f"Invalid input parameter: {str(e)}") from e + except Exception as e: + # Catch any other unexpected exceptions + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") from e + + +async def set_agent_featured( + agent_id: str, is_active: bool = True, featured_categories: list[str] = ["featured"] +) -> prisma.models.FeaturedAgent: + """Set an agent as featured in the database. + + Args: + agent_id (str): The ID of the agent. + category (str, optional): The category to set the agent as featured. Defaults to "featured". + + Raises: + AgentQueryError: If there is an error setting the agent as featured. + """ + try: + agent = await prisma.models.Agents.prisma().find_unique(where={"id": agent_id}) + if not agent: + raise AgentQueryError(f"Agent with ID {agent_id} not found.") + + featured = await prisma.models.FeaturedAgent.prisma().upsert( + where={"agentId": agent_id}, + data={ + "update": { + "featuredCategories": featured_categories, + "isActive": is_active, + }, + "create": { + "featuredCategories": featured_categories, + "isActive": is_active, + "agent": {"connect": {"id": agent_id}}, + }, + }, + ) + return featured + + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_featured_agents( + category: str = "featured", + page: int = 1, + page_size: int = 10, + submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED, +) -> FeaturedAgentResponse: + """Retrieve a list of featured agents from the database based on the provided category. + + Args: + category (str, optional): The category of featured agents to retrieve. Defaults to "featured". + page (int, optional): The page number to retrieve. Defaults to 1. + page_size (int, optional): The number of agents per page. Defaults to 10. + + Returns: + dict: A dictionary containing the list of featured agents, total count, current page number, page size, and total number of pages. + """ + try: + # Calculate pagination + skip = (page - 1) * page_size + + # Execute the query + try: + featured_agents = await prisma.models.FeaturedAgent.prisma().find_many( + where={ + "featuredCategories": {"has": category}, + "isActive": True, + "agent": {"is": {"submissionStatus": submission_status}}, + }, + include={"agent": {"include": {"AnalyticsTracker": True}}}, + skip=skip, + take=page_size, + ) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + + # Get total count for pagination info + total_count = len(featured_agents) + + return FeaturedAgentResponse( + featured_agents=featured_agents, + total_count=total_count, + page=page, + page_size=page_size, + total_pages=(total_count + page_size - 1) // page_size, + ) + + except AgentQueryError as e: + # Log the error or handle it as needed + raise e from e + except ValueError as e: + raise AgentQueryError(f"Invalid input parameter: {str(e)}") from e + except Exception as e: + # Catch any other unexpected exceptions + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") from e + + +async def remove_featured_category( + agent_id: str, category: str +) -> prisma.models.FeaturedAgent | None: + """Adds a featured category to an agent. + + Args: + agent_id (str): The ID of the agent. + category (str): The category to add to the agent. + + Returns: + FeaturedAgentResponse: The updated list of featured agents. + """ + try: + # get the existing categories + featured_agent = await prisma.models.FeaturedAgent.prisma().find_unique( + where={"agentId": agent_id}, + include={"agent": True}, + ) + + if not featured_agent: + raise AgentQueryError(f"Agent with ID {agent_id} not found.") + + # remove the category from the list + featured_agent.featuredCategories.remove(category) + + featured_agent = await prisma.models.FeaturedAgent.prisma().update( + where={"agentId": agent_id}, + data={"featuredCategories": featured_agent.featuredCategories}, + ) + + return featured_agent + + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def add_featured_category( + agent_id: str, category: str +) -> prisma.models.FeaturedAgent | None: + """Removes a featured category from an agent. + + Args: + agent_id (str): The ID of the agent. + category (str): The category to remove from the agent. + + Returns: + FeaturedAgentResponse: The updated list of featured agents. + """ + try: + featured_agent = await prisma.models.FeaturedAgent.prisma().update( + where={"agentId": agent_id}, + data={"featuredCategories": {"push": [category]}}, + ) + + return featured_agent + + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_agent_featured(agent_id: str) -> prisma.models.FeaturedAgent | None: + """Retrieve an agent's featured categories from the database. + + Args: + agent_id (str): The ID of the agent. + + Returns: + FeaturedAgentResponse: The list of featured agents. + """ + try: + featured_agent = await prisma.models.FeaturedAgent.prisma().find_unique( + where={"agentId": agent_id}, + ) + return featured_agent + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_not_featured_agents( + page: int = 1, page_size: int = 10 +) -> typing.List[prisma.models.Agents]: + """ + Retrieve a list of not featured agents from the database. + """ + try: + agents = await prisma.client.get_client().query_raw( + query=f""" + SELECT + "market"."Agents".id, + "market"."Agents"."createdAt", + "market"."Agents"."updatedAt", + "market"."Agents".version, + "market"."Agents".name, + LEFT("market"."Agents".description, 500) AS description, + "market"."Agents".author, + "market"."Agents".keywords, + "market"."Agents".categories, + "market"."Agents".graph, + "market"."Agents"."submissionStatus", + "market"."Agents"."submissionDate", + "market"."Agents".search::text AS search + FROM "market"."Agents" + LEFT JOIN "market"."FeaturedAgent" ON "market"."Agents"."id" = "market"."FeaturedAgent"."agentId" + WHERE ("market"."FeaturedAgent"."agentId" IS NULL OR "market"."FeaturedAgent"."featuredCategories" = '{{}}') + AND "market"."Agents"."submissionStatus" = 'APPROVED' + ORDER BY "market"."Agents"."createdAt" DESC + LIMIT {page_size} OFFSET {page_size * (page - 1)} + """, + model=prisma.models.Agents, + ) + return agents + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + + +async def get_all_categories() -> market.model.CategoriesResponse: + """ + Retrieve all unique categories from the database. + + Returns: + CategoriesResponse: A list of unique categories. + """ + try: + agents = await prisma.models.Agents.prisma().find_many(distinct=["categories"]) + + # Aggregate categories on the Python side + all_categories = set() + for agent in agents: + all_categories.update(agent.categories) + + unique_categories = sorted(list(all_categories)) + + return market.model.CategoriesResponse(unique_categories=unique_categories) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception: + # Return an empty list of categories in case of unexpected errors + return market.model.CategoriesResponse(unique_categories=[]) + + +async def create_agent_installed_event( + event_data: market.model.AgentInstalledFromMarketplaceEventData, +): + try: + await prisma.models.InstallTracker.prisma().create( + data={ + "installedAgentId": event_data.installed_agent_id, + "marketplaceAgentId": event_data.marketplace_agent_id, + "installationLocation": prisma.enums.InstallationLocation( + event_data.installation_location.name + ), + } + ) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + raise AgentQueryError(f"Unexpected error occurred: {str(e)}") diff --git a/autogpt_platform/market/market/model.py b/autogpt_platform/market/market/model.py new file mode 100644 index 000000000..14bd017a1 --- /dev/null +++ b/autogpt_platform/market/market/model.py @@ -0,0 +1,161 @@ +import datetime +import typing +from enum import Enum +from typing import Generic, Literal, TypeVar, Union + +import prisma.enums +import pydantic + + +class InstallationLocation(str, Enum): + LOCAL = "local" + CLOUD = "cloud" + + +class AgentInstalledFromMarketplaceEventData(pydantic.BaseModel): + marketplace_agent_id: str + installed_agent_id: str + installation_location: InstallationLocation + + +class AgentInstalledFromTemplateEventData(pydantic.BaseModel): + template_id: str + installed_agent_id: str + installation_location: InstallationLocation + + +class AgentInstalledFromMarketplaceEvent(pydantic.BaseModel): + event_name: Literal["agent_installed_from_marketplace"] + event_data: AgentInstalledFromMarketplaceEventData + + +class AgentInstalledFromTemplateEvent(pydantic.BaseModel): + event_name: Literal["agent_installed_from_template"] + event_data: AgentInstalledFromTemplateEventData + + +AnalyticsEvent = Union[ + AgentInstalledFromMarketplaceEvent, AgentInstalledFromTemplateEvent +] + + +class AnalyticsRequest(pydantic.BaseModel): + event: AnalyticsEvent + + +class AddAgentRequest(pydantic.BaseModel): + graph: dict[str, typing.Any] + author: str + keywords: list[str] + categories: list[str] + + +class SubmissionReviewRequest(pydantic.BaseModel): + agent_id: str + version: int + status: prisma.enums.SubmissionStatus + comments: str | None + + +class AgentResponse(pydantic.BaseModel): + """ + Represents a response from an agent. + + Attributes: + id (str): The ID of the agent. + name (str, optional): The name of the agent. + description (str, optional): The description of the agent. + author (str, optional): The author of the agent. + keywords (list[str]): The keywords associated with the agent. + categories (list[str]): The categories the agent belongs to. + version (int): The version of the agent. + createdAt (str): The creation date of the agent. + updatedAt (str): The last update date of the agent. + """ + + id: str + name: typing.Optional[str] + description: typing.Optional[str] + author: typing.Optional[str] + keywords: list[str] + categories: list[str] + version: int + createdAt: datetime.datetime + updatedAt: datetime.datetime + submissionStatus: str + views: int = 0 + downloads: int = 0 + + +class AgentDetailResponse(pydantic.BaseModel): + """ + Represents the response data for an agent detail. + + Attributes: + id (str): The ID of the agent. + name (Optional[str]): The name of the agent. + description (Optional[str]): The description of the agent. + author (Optional[str]): The author of the agent. + keywords (List[str]): The keywords associated with the agent. + categories (List[str]): The categories the agent belongs to. + version (int): The version of the agent. + createdAt (str): The creation date of the agent. + updatedAt (str): The last update date of the agent. + graph (Dict[str, Any]): The graph data of the agent. + """ + + id: str + name: typing.Optional[str] + description: typing.Optional[str] + author: typing.Optional[str] + keywords: list[str] + categories: list[str] + version: int + createdAt: datetime.datetime + updatedAt: datetime.datetime + graph: dict[str, typing.Any] + + +class FeaturedAgentResponse(pydantic.BaseModel): + """ + Represents the response data for an agent detail. + """ + + agentId: str + featuredCategories: list[str] + createdAt: datetime.datetime + updatedAt: datetime.datetime + isActive: bool + + +class CategoriesResponse(pydantic.BaseModel): + """ + Represents the response data for a list of categories. + + Attributes: + unique_categories (list[str]): The list of unique categories. + """ + + unique_categories: list[str] + + +T = TypeVar("T") + + +class ListResponse(pydantic.BaseModel, Generic[T]): + """ + Represents a list response. + + Attributes: + items (list[T]): The list of items. + total_count (int): The total count of items. + page (int): The current page number. + page_size (int): The number of items per page. + total_pages (int): The total number of pages. + """ + + items: list[T] + total_count: int + page: int + page_size: int + total_pages: int diff --git a/autogpt_platform/market/market/routes/admin.py b/autogpt_platform/market/market/routes/admin.py new file mode 100644 index 000000000..a3fe65cc9 --- /dev/null +++ b/autogpt_platform/market/market/routes/admin.py @@ -0,0 +1,286 @@ +import logging +import typing + +import autogpt_libs.auth +import fastapi +import prisma +import prisma.enums +import prisma.models + +import market.db +import market.model + +logger = logging.getLogger("marketplace") + +router = fastapi.APIRouter() + + +@router.delete("/agent/{agent_id}", response_model=market.model.AgentResponse) +async def delete_agent( + agent_id: str, + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +): + """ + Delete an agent and all related records from the database. + + Args: + agent_id (str): The ID of the agent to delete. + + Returns: + market.model.AgentResponse: The deleted agent's data. + + Raises: + fastapi.HTTPException: If the agent is not found or if there's an error during deletion. + """ + try: + deleted_agent = await market.db.delete_agent(agent_id) + if deleted_agent: + return market.model.AgentResponse(**deleted_agent.dict()) + else: + raise fastapi.HTTPException(status_code=404, detail="Agent not found") + except market.db.AgentQueryError as e: + logger.error(f"Error deleting agent: {e}") + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + logger.error(f"Unexpected error deleting agent: {e}") + raise fastapi.HTTPException( + status_code=500, detail="An unexpected error occurred" + ) + + +@router.post("/agent", response_model=market.model.AgentResponse) +async def create_agent_entry( + request: market.model.AddAgentRequest, + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +): + """ + A basic endpoint to create a new agent entry in the database. + + """ + try: + agent = await market.db.create_agent_entry( + request.graph["name"], + request.graph["description"], + request.author, + request.keywords, + request.categories, + prisma.Json(request.graph), + ) + + return fastapi.responses.PlainTextResponse(agent.model_dump_json()) + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + + +@router.post("/agent/featured/{agent_id}") +async def set_agent_featured( + agent_id: str, + categories: list[str] = fastapi.Query( + default=["featured"], + description="The categories to set the agent as featured in", + ), + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +) -> market.model.FeaturedAgentResponse: + """ + A basic endpoint to set an agent as featured in the database. + """ + try: + agent = await market.db.set_agent_featured( + agent_id, is_active=True, featured_categories=categories + ) + return market.model.FeaturedAgentResponse(**agent.model_dump()) + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + + +@router.get("/agent/featured/{agent_id}") +async def get_agent_featured( + agent_id: str, + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +) -> market.model.FeaturedAgentResponse | None: + """ + A basic endpoint to get an agent as featured in the database. + """ + try: + agent = await market.db.get_agent_featured(agent_id) + if agent: + return market.model.FeaturedAgentResponse(**agent.model_dump()) + else: + return None + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + + +@router.delete("/agent/featured/{agent_id}") +async def unset_agent_featured( + agent_id: str, + category: str = "featured", + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +) -> market.model.FeaturedAgentResponse | None: + """ + A basic endpoint to unset an agent as featured in the database. + """ + try: + featured = await market.db.remove_featured_category(agent_id, category=category) + if featured: + return market.model.FeaturedAgentResponse(**featured.model_dump()) + else: + return None + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + + +@router.get("/agent/not-featured") +async def get_not_featured_agents( + page: int = fastapi.Query(1, ge=1, description="Page number"), + page_size: int = fastapi.Query( + 10, ge=1, le=100, description="Number of items per page" + ), + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +) -> market.model.ListResponse[market.model.AgentResponse]: + """ + A basic endpoint to get all not featured agents in the database. + """ + try: + agents = await market.db.get_not_featured_agents(page=page, page_size=page_size) + return market.model.ListResponse( + items=[ + market.model.AgentResponse(**agent.model_dump()) for agent in agents + ], + total_count=len(agents), + page=page, + page_size=page_size, + total_pages=999, + ) + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + + +@router.get( + "/agent/submissions", + response_model=market.model.ListResponse[market.model.AgentResponse], +) +async def get_agent_submissions( + page: int = fastapi.Query(1, ge=1, description="Page number"), + page_size: int = fastapi.Query( + 10, ge=1, le=100, description="Number of items per page" + ), + name: typing.Optional[str] = fastapi.Query( + None, description="Filter by agent name" + ), + keyword: typing.Optional[str] = fastapi.Query( + None, description="Filter by keyword" + ), + category: typing.Optional[str] = fastapi.Query( + None, description="Filter by category" + ), + description: typing.Optional[str] = fastapi.Query( + None, description="Fuzzy search in description" + ), + description_threshold: int = fastapi.Query( + 60, ge=0, le=100, description="Fuzzy search threshold" + ), + sort_by: str = fastapi.Query("createdAt", description="Field to sort by"), + sort_order: typing.Literal["asc", "desc"] = fastapi.Query( + "desc", description="Sort order (asc or desc)" + ), + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +) -> market.model.ListResponse[market.model.AgentResponse]: + logger.info("Getting agent submissions") + try: + result = await market.db.get_agents( + page=page, + page_size=page_size, + name=name, + keyword=keyword, + category=category, + description=description, + description_threshold=description_threshold, + sort_by=sort_by, + sort_order=sort_order, + submission_status=prisma.enums.SubmissionStatus.PENDING, + ) + + agents = [ + market.model.AgentResponse(**agent.dict()) for agent in result["agents"] + ] + + return market.model.ListResponse( + items=agents, + total_count=result["total_count"], + page=result["page"], + page_size=result["page_size"], + total_pages=result["total_pages"], + ) + + except market.db.AgentQueryError as e: + logger.error(f"Error getting agent submissions: {e}") + raise fastapi.HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"Error getting agent submissions: {e}") + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {e}" + ) + + +@router.post("/agent/submissions") +async def review_submission( + review_request: market.model.SubmissionReviewRequest, + user: autogpt_libs.auth.User = fastapi.Depends( + autogpt_libs.auth.requires_admin_user + ), +) -> prisma.models.Agents | None: + """ + A basic endpoint to review a submission in the database. + """ + logger.info( + f"Reviewing submission: {review_request.agent_id}, {review_request.version}" + ) + try: + agent = await market.db.update_agent_entry( + agent_id=review_request.agent_id, + version=review_request.version, + submission_state=review_request.status, + comments=review_request.comments, + ) + return agent + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + + +@router.get("/categories") +async def get_categories() -> market.model.CategoriesResponse: + """ + A basic endpoint to get all available categories. + """ + try: + categories = await market.db.get_all_categories() + return categories + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) diff --git a/autogpt_platform/market/market/routes/admin_tests.py b/autogpt_platform/market/market/routes/admin_tests.py new file mode 100644 index 000000000..c305e8c83 --- /dev/null +++ b/autogpt_platform/market/market/routes/admin_tests.py @@ -0,0 +1,76 @@ +import datetime +from unittest import mock + +import autogpt_libs.auth.middleware +import fastapi +import fastapi.testclient +import prisma.enums +import prisma.models + +import market.app + +client = fastapi.testclient.TestClient(market.app.app) + + +async def override_auth_middleware(request: fastapi.Request): + return {"sub": "3e53486c-cf57-477e-ba2a-cb02dc828e1a", "role": "admin"} + + +market.app.app.dependency_overrides[autogpt_libs.auth.middleware.auth_middleware] = ( + override_auth_middleware +) + + +def test_get_submissions(): + with mock.patch("market.db.get_agents") as mock_get_agents: + mock_get_agents.return_value = { + "agents": [], + "total_count": 0, + "page": 1, + "page_size": 10, + "total_pages": 0, + } + response = client.get( + "/api/v1/market/admin/agent/submissions?page=1&page_size=10&description_threshold=60&sort_by=createdAt&sort_order=desc", + headers={"Bearer": ""}, + ) + assert response.status_code == 200 + assert response.json() == { + "agents": [], + "total_count": 0, + "page": 1, + "page_size": 10, + "total_pages": 0, + } + + +def test_review_submission(): + with mock.patch("market.db.update_agent_entry") as mock_update_agent_entry: + mock_update_agent_entry.return_value = prisma.models.Agents( + id="aaa-bbb-ccc", + version=1, + createdAt=datetime.datetime.fromisoformat("2021-10-01T00:00:00+00:00"), + updatedAt=datetime.datetime.fromisoformat("2021-10-01T00:00:00+00:00"), + submissionStatus=prisma.enums.SubmissionStatus.APPROVED, + submissionDate=datetime.datetime.fromisoformat("2021-10-01T00:00:00+00:00"), + submissionReviewComments="Looks good", + submissionReviewDate=datetime.datetime.fromisoformat( + "2021-10-01T00:00:00+00:00" + ), + keywords=["test"], + categories=["test"], + graph='{"name": "test", "description": "test"}', # type: ignore + ) + response = client.post( + "/api/v1/market/admin/agent/submissions", + headers={ + "Authorization": "Bearer token" + }, # Assuming you need an authorization token + json={ + "agent_id": "aaa-bbb-ccc", + "version": 1, + "status": "APPROVED", + "comments": "Looks good", + }, + ) + assert response.status_code == 200 diff --git a/autogpt_platform/market/market/routes/agents.py b/autogpt_platform/market/market/routes/agents.py new file mode 100644 index 000000000..672dfb64e --- /dev/null +++ b/autogpt_platform/market/market/routes/agents.py @@ -0,0 +1,368 @@ +import json +import tempfile +import typing + +import fastapi +import fastapi.responses +import prisma +import prisma.enums + +import market.db +import market.model +import market.utils.analytics + +router = fastapi.APIRouter() + + +@router.get( + "/agents", response_model=market.model.ListResponse[market.model.AgentResponse] +) +async def list_agents( + page: int = fastapi.Query(1, ge=1, description="Page number"), + page_size: int = fastapi.Query( + 10, ge=1, le=100, description="Number of items per page" + ), + name: typing.Optional[str] = fastapi.Query( + None, description="Filter by agent name" + ), + keyword: typing.Optional[str] = fastapi.Query( + None, description="Filter by keyword" + ), + category: typing.Optional[str] = fastapi.Query( + None, description="Filter by category" + ), + description: typing.Optional[str] = fastapi.Query( + None, description="Fuzzy search in description" + ), + description_threshold: int = fastapi.Query( + 60, ge=0, le=100, description="Fuzzy search threshold" + ), + sort_by: str = fastapi.Query("createdAt", description="Field to sort by"), + sort_order: typing.Literal["asc", "desc"] = fastapi.Query( + "desc", description="Sort order (asc or desc)" + ), + submission_status: prisma.enums.SubmissionStatus = fastapi.Query( + default=prisma.enums.SubmissionStatus.APPROVED, + description="Filter by submission status", + ), +): + """ + Retrieve a list of agents based on the provided filters. + + Args: + page (int): Page number (default: 1). + page_size (int): Number of items per page (default: 10, min: 1, max: 100). + name (str, optional): Filter by agent name. + keyword (str, optional): Filter by keyword. + category (str, optional): Filter by category. + description (str, optional): Fuzzy search in description. + description_threshold (int): Fuzzy search threshold (default: 60, min: 0, max: 100). + sort_by (str): Field to sort by (default: "createdAt"). + sort_order (str): Sort order (asc or desc) (default: "desc"). + submission_status (str): Filter by submission status (default: "APPROVED"). + + Returns: + market.model.ListResponse[market.model.AgentResponse]: A response containing the list of agents and pagination information. + + Raises: + HTTPException: If there is a client error (status code 400) or an unexpected error (status code 500). + """ + try: + result = await market.db.get_agents( + page=page, + page_size=page_size, + name=name, + keyword=keyword, + category=category, + description=description, + description_threshold=description_threshold, + sort_by=sort_by, + sort_order=sort_order, + submission_status=submission_status, + ) + + agents = [ + market.model.AgentResponse(**agent.dict()) for agent in result["agents"] + ] + + return market.model.ListResponse( + items=agents, + total_count=result["total_count"], + page=result["page"], + page_size=result["page_size"], + total_pages=result["total_pages"], + ) + + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {e}" + ) + + +@router.get("/agents/{agent_id}", response_model=market.model.AgentDetailResponse) +async def get_agent_details_endpoint( + background_tasks: fastapi.BackgroundTasks, + agent_id: str = fastapi.Path(..., description="The ID of the agent to retrieve"), + version: typing.Optional[int] = fastapi.Query( + None, description="Specific version of the agent" + ), +): + """ + Retrieve details of a specific agent. + + Args: + agent_id (str): The ID of the agent to retrieve. + version (Optional[int]): Specific version of the agent (default: None). + + Returns: + market.model.AgentDetailResponse: The response containing the agent details. + + Raises: + HTTPException: If the agent is not found or an unexpected error occurs. + """ + try: + agent = await market.db.get_agent_details(agent_id, version) + background_tasks.add_task(market.utils.analytics.track_view, agent_id) + return market.model.AgentDetailResponse(**agent.model_dump()) + + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {str(e)}" + ) + + +@router.get("/agents/{agent_id}/download") +async def download_agent( + background_tasks: fastapi.BackgroundTasks, + agent_id: str = fastapi.Path(..., description="The ID of the agent to retrieve"), + version: typing.Optional[int] = fastapi.Query( + None, description="Specific version of the agent" + ), +): + """ + Download details of a specific agent. + + NOTE: This is the same as agent details, however it also triggers + the "download" tracking. We don't actually want to download a file though + + Args: + agent_id (str): The ID of the agent to retrieve. + version (Optional[int]): Specific version of the agent (default: None). + + Returns: + market.model.AgentDetailResponse: The response containing the agent details. + + Raises: + HTTPException: If the agent is not found or an unexpected error occurs. + """ + try: + agent = await market.db.get_agent_details(agent_id, version) + background_tasks.add_task(market.utils.analytics.track_download, agent_id) + return market.model.AgentDetailResponse(**agent.model_dump()) + + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=404, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {str(e)}" + ) + + +@router.get("/agents/{agent_id}/download-file") +async def download_agent_file( + background_tasks: fastapi.BackgroundTasks, + agent_id: str = fastapi.Path(..., description="The ID of the agent to download"), + version: typing.Optional[int] = fastapi.Query( + None, description="Specific version of the agent" + ), +) -> fastapi.responses.FileResponse: + """ + Download the agent file by streaming its content. + + Args: + agent_id (str): The ID of the agent to download. + version (Optional[int]): Specific version of the agent to download. + + Returns: + StreamingResponse: A streaming response containing the agent's graph data. + + Raises: + HTTPException: If the agent is not found or an unexpected error occurs. + """ + agent = await market.db.get_agent_details(agent_id, version) + + graph_data: prisma.Json = agent.graph + + background_tasks.add_task(market.utils.analytics.track_download, agent_id) + + file_name = f"agent_{agent_id}_v{version or 'latest'}.json" + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as tmp_file: + tmp_file.write(json.dumps(graph_data)) + tmp_file.flush() + + return fastapi.responses.FileResponse( + tmp_file.name, filename=file_name, media_type="application/json" + ) + + +# top agents by downloads +@router.get( + "/top-downloads/agents", + response_model=market.model.ListResponse[market.model.AgentResponse], +) +async def top_agents_by_downloads( + page: int = fastapi.Query(1, ge=1, description="Page number"), + page_size: int = fastapi.Query( + 10, ge=1, le=100, description="Number of items per page" + ), + submission_status: prisma.enums.SubmissionStatus = fastapi.Query( + default=prisma.enums.SubmissionStatus.APPROVED, + description="Filter by submission status", + ), +) -> market.model.ListResponse[market.model.AgentResponse]: + """ + Retrieve a list of top agents based on the number of downloads. + + Args: + page (int): Page number (default: 1). + page_size (int): Number of items per page (default: 10, min: 1, max: 100). + submission_status (str): Filter by submission status (default: "APPROVED"). + + Returns: + market.model.ListResponse[market.model.AgentResponse]: A response containing the list of top agents and pagination information. + + Raises: + HTTPException: If there is a client error (status code 400) or an unexpected error (status code 500). + """ + try: + result = await market.db.get_top_agents_by_downloads( + page=page, + page_size=page_size, + submission_status=submission_status, + ) + + ret = market.model.ListResponse( + total_count=result.total_count, + page=result.page, + page_size=result.page_size, + total_pages=result.total_pages, + items=[ + market.model.AgentResponse( + id=item.agent.id, + name=item.agent.name, + description=item.agent.description, + author=item.agent.author, + keywords=item.agent.keywords, + categories=item.agent.categories, + version=item.agent.version, + createdAt=item.agent.createdAt, + updatedAt=item.agent.updatedAt, + views=item.views, + downloads=item.downloads, + submissionStatus=item.agent.submissionStatus, + ) + for item in result.items + if item.agent is not None + ], + ) + + return ret + + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) from e + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {e}" + ) from e + + +@router.get( + "/featured/agents", + response_model=market.model.ListResponse[market.model.AgentResponse], +) +async def get_featured_agents( + category: str = fastapi.Query( + "featured", description="Category of featured agents" + ), + page: int = fastapi.Query(1, ge=1, description="Page number"), + page_size: int = fastapi.Query( + 10, ge=1, le=100, description="Number of items per page" + ), + submission_status: prisma.enums.SubmissionStatus = fastapi.Query( + default=prisma.enums.SubmissionStatus.APPROVED, + description="Filter by submission status", + ), +): + """ + Retrieve a list of featured agents based on the provided category. + + Args: + category (str): Category of featured agents (default: "featured"). + page (int): Page number (default: 1). + page_size (int): Number of items per page (default: 10, min: 1, max: 100). + submission_status (str): Filter by submission status (default: "APPROVED"). + + Returns: + market.model.ListResponse[market.model.AgentResponse]: A response containing the list of featured agents and pagination information. + + Raises: + HTTPException: If there is a client error (status code 400) or an unexpected error (status code 500). + """ + try: + result = await market.db.get_featured_agents( + category=category, + page=page, + page_size=page_size, + submission_status=submission_status, + ) + + ret = market.model.ListResponse( + total_count=result.total_count, + page=result.page, + page_size=result.page_size, + total_pages=result.total_pages, + items=[ + market.model.AgentResponse( + id=item.agent.id, + name=item.agent.name, + description=item.agent.description, + author=item.agent.author, + keywords=item.agent.keywords, + categories=item.agent.categories, + version=item.agent.version, + createdAt=item.agent.createdAt, + updatedAt=item.agent.updatedAt, + views=( + item.agent.AnalyticsTracker[0].views + if item.agent.AnalyticsTracker + and len(item.agent.AnalyticsTracker) > 0 + else 0 + ), + downloads=( + item.agent.AnalyticsTracker[0].downloads + if item.agent.AnalyticsTracker + and len(item.agent.AnalyticsTracker) > 0 + else 0 + ), + submissionStatus=item.agent.submissionStatus, + ) + for item in result.featured_agents + if item.agent is not None + ], + ) + + return ret + + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) from e + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {e}" + ) from e diff --git a/autogpt_platform/market/market/routes/analytics.py b/autogpt_platform/market/market/routes/analytics.py new file mode 100644 index 000000000..87adbca07 --- /dev/null +++ b/autogpt_platform/market/market/routes/analytics.py @@ -0,0 +1,26 @@ +import fastapi + +import market.db +import market.model + +router = fastapi.APIRouter() + + +@router.post("/agent-installed") +async def agent_installed_endpoint( + event_data: market.model.AgentInstalledFromMarketplaceEventData, +): + """ + Endpoint to track agent installation events from the marketplace. + + Args: + event_data (market.model.AgentInstalledFromMarketplaceEventData): The event data. + """ + try: + await market.db.create_agent_installed_event(event_data) + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {e}" + ) diff --git a/autogpt_platform/market/market/routes/search.py b/autogpt_platform/market/market/routes/search.py new file mode 100644 index 000000000..15ef3ffa3 --- /dev/null +++ b/autogpt_platform/market/market/routes/search.py @@ -0,0 +1,56 @@ +import typing + +import fastapi +import prisma.enums + +import market.db +import market.model +import market.utils.extension_types + +router = fastapi.APIRouter() + + +@router.get("/search") +async def search( + query: str, + page: int = fastapi.Query(1, description="The pagination page to start on"), + page_size: int = fastapi.Query( + 10, description="The number of items to return per page" + ), + categories: typing.List[str] = fastapi.Query( + None, description="The categories to filter by" + ), + description_threshold: int = fastapi.Query( + 60, description="The number of characters to return from the description" + ), + sort_by: str = fastapi.Query("rank", description="Sorting by column"), + sort_order: typing.Literal["desc", "asc"] = fastapi.Query( + "desc", description="The sort order based on sort_by" + ), + submission_status: prisma.enums.SubmissionStatus = fastapi.Query( + prisma.enums.SubmissionStatus.APPROVED, + description="The submission status to filter by", + ), +) -> market.model.ListResponse[market.utils.extension_types.AgentsWithRank]: + """searches endpoint for agents + + Args: + query (str): the search query + page (int, optional): the pagination page to start on. Defaults to 1. + page_size (int, optional): the number of items to return per page. Defaults to 10. + category (str | None, optional): the agent category to filter by. None is no filter. Defaults to None. + description_threshold (int, optional): the number of characters to return from the description. Defaults to 60. + sort_by (str, optional): Sorting by column. Defaults to "rank". + sort_order ('asc' | 'desc', optional): the sort order based on sort_by. Defaults to "desc". + """ + agents = await market.db.search_db( + query=query, + page=page, + page_size=page_size, + categories=categories, + description_threshold=description_threshold, + sort_by=sort_by, + sort_order=sort_order, + submission_status=submission_status, + ) + return agents diff --git a/autogpt_platform/market/market/routes/submissions.py b/autogpt_platform/market/market/routes/submissions.py new file mode 100644 index 000000000..49a4dea14 --- /dev/null +++ b/autogpt_platform/market/market/routes/submissions.py @@ -0,0 +1,35 @@ +import autogpt_libs.auth +import fastapi +import fastapi.responses +import prisma + +import market.db +import market.model +import market.utils.analytics + +router = fastapi.APIRouter() + + +@router.post("/agents/submit", response_model=market.model.AgentResponse) +async def submit_agent( + request: market.model.AddAgentRequest, + user: autogpt_libs.auth.User = fastapi.Depends(autogpt_libs.auth.requires_user), +): + """ + A basic endpoint to create a new agent entry in the database. + """ + try: + agent = await market.db.create_agent_entry( + request.graph["name"], + request.graph["description"], + request.author, + request.keywords, + request.categories, + prisma.Json(request.graph), + ) + + return fastapi.responses.PlainTextResponse(agent.model_dump_json()) + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException(status_code=500, detail=str(e)) diff --git a/autogpt_platform/market/market/utils/analytics.py b/autogpt_platform/market/market/utils/analytics.py new file mode 100644 index 000000000..71dcaf078 --- /dev/null +++ b/autogpt_platform/market/market/utils/analytics.py @@ -0,0 +1,47 @@ +import prisma.models + + +async def track_download(agent_id: str): + """ + Track the download event in the database. + + Args: + agent_id (str): The ID of the agent. + version (int | None, optional): The version of the agent. Defaults to None. + + Raises: + Exception: If there is an error tracking the download event. + """ + try: + await prisma.models.AnalyticsTracker.prisma().upsert( + where={"agentId": agent_id}, + data={ + "update": {"downloads": {"increment": 1}}, + "create": {"agentId": agent_id, "downloads": 1, "views": 0}, + }, + ) + except Exception as e: + raise Exception(f"Error tracking download event: {str(e)}") + + +async def track_view(agent_id: str): + """ + Track the view event in the database. + + Args: + agent_id (str): The ID of the agent. + version (int | None, optional): The version of the agent. Defaults to None. + + Raises: + Exception: If there is an error tracking the view event. + """ + try: + await prisma.models.AnalyticsTracker.prisma().upsert( + where={"agentId": agent_id}, + data={ + "update": {"views": {"increment": 1}}, + "create": {"agentId": agent_id, "downloads": 0, "views": 1}, + }, + ) + except Exception as e: + raise Exception(f"Error tracking view event: {str(e)}") diff --git a/autogpt_platform/market/market/utils/extension_types.py b/autogpt_platform/market/market/utils/extension_types.py new file mode 100644 index 000000000..d76bbb19f --- /dev/null +++ b/autogpt_platform/market/market/utils/extension_types.py @@ -0,0 +1,5 @@ +import prisma.models + + +class AgentsWithRank(prisma.models.Agents): + rank: float diff --git a/autogpt_platform/market/market/utils/partial_types.py b/autogpt_platform/market/market/utils/partial_types.py new file mode 100644 index 000000000..3ec3bdc2b --- /dev/null +++ b/autogpt_platform/market/market/utils/partial_types.py @@ -0,0 +1,6 @@ +import prisma.models + +prisma.models.Agents.create_partial( + "AgentOnlyDescriptionNameAuthorIdCategories", + include={"name", "author", "id", "categories"}, +) diff --git a/autogpt_platform/market/migrations/20240731181721_base_migration/migration.sql b/autogpt_platform/market/migrations/20240731181721_base_migration/migration.sql new file mode 100644 index 000000000..57bdb6d04 --- /dev/null +++ b/autogpt_platform/market/migrations/20240731181721_base_migration/migration.sql @@ -0,0 +1,61 @@ +-- CreateTable +CREATE TABLE "Agents" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "version" INTEGER NOT NULL DEFAULT 1, + "name" TEXT, + "description" TEXT, + "author" TEXT, + "keywords" TEXT[], + "categories" TEXT[], + "search" tsvector DEFAULT ''::tsvector, + "graph" JSONB NOT NULL, + + CONSTRAINT "Agents_pkey" PRIMARY KEY ("id","version") +); + +-- CreateTable +CREATE TABLE "AnalyticsTracker" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "agentId" UUID NOT NULL, + "views" INTEGER NOT NULL, + "downloads" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Agents_id_key" ON "Agents"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "AnalyticsTracker_id_key" ON "AnalyticsTracker"("id"); + +-- AddForeignKey +ALTER TABLE "AnalyticsTracker" ADD CONSTRAINT "AnalyticsTracker_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + + +-- Add trigger to update the search column with the tsvector of the agent +-- Function to be invoked by trigger + +CREATE OR REPLACE FUNCTION update_tsvector_column() RETURNS TRIGGER AS $$ + +BEGIN + +NEW.search := to_tsvector('english', COALESCE(NEW.description, '')|| ' ' ||COALESCE(NEW.name, '')|| ' ' ||COALESCE(NEW.author, '')); + +RETURN NEW; + +END; + +$$ LANGUAGE plpgsql SECURITY definer SET search_path = public, pg_temp; + +-- Trigger that keeps the TSVECTOR up to date + +DROP TRIGGER IF EXISTS "update_tsvector" ON "Agents"; + +CREATE TRIGGER "update_tsvector" + +BEFORE INSERT OR UPDATE ON "Agents" + +FOR EACH ROW + +EXECUTE FUNCTION update_tsvector_column (); \ No newline at end of file diff --git a/autogpt_platform/market/migrations/20240731213728_unique_agent_id/migration.sql b/autogpt_platform/market/migrations/20240731213728_unique_agent_id/migration.sql new file mode 100644 index 000000000..fd421f6a6 --- /dev/null +++ b/autogpt_platform/market/migrations/20240731213728_unique_agent_id/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - A unique constraint covering the columns `[agentId]` on the table `AnalyticsTracker` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "AnalyticsTracker" ADD CONSTRAINT "AnalyticsTracker_pkey" PRIMARY KEY ("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "AnalyticsTracker_agentId_key" ON "AnalyticsTracker"("agentId"); diff --git a/autogpt_platform/market/migrations/20240802100955_add_featured_agents/migration.sql b/autogpt_platform/market/migrations/20240802100955_add_featured_agents/migration.sql new file mode 100644 index 000000000..e615e8671 --- /dev/null +++ b/autogpt_platform/market/migrations/20240802100955_add_featured_agents/migration.sql @@ -0,0 +1,20 @@ +-- CreateTable +CREATE TABLE "FeaturedAgent" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "agentId" UUID NOT NULL, + "is_featured" BOOLEAN NOT NULL, + "category" TEXT NOT NULL DEFAULT 'featured', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FeaturedAgent_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "FeaturedAgent_id_key" ON "FeaturedAgent"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "FeaturedAgent_agentId_key" ON "FeaturedAgent"("agentId"); + +-- AddForeignKey +ALTER TABLE "FeaturedAgent" ADD CONSTRAINT "FeaturedAgent_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/autogpt_platform/market/migrations/20240802174953_default_is_featured_to_false/migration.sql b/autogpt_platform/market/migrations/20240802174953_default_is_featured_to_false/migration.sql new file mode 100644 index 000000000..0893c3785 --- /dev/null +++ b/autogpt_platform/market/migrations/20240802174953_default_is_featured_to_false/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "FeaturedAgent" ALTER COLUMN "is_featured" SET DEFAULT false; diff --git a/autogpt_platform/market/migrations/20240808080208_added_submissions/migration.sql b/autogpt_platform/market/migrations/20240808080208_added_submissions/migration.sql new file mode 100644 index 000000000..e30f090fb --- /dev/null +++ b/autogpt_platform/market/migrations/20240808080208_added_submissions/migration.sql @@ -0,0 +1,8 @@ +-- CreateEnum +CREATE TYPE "SubmissionStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED'); + +-- AlterTable +ALTER TABLE "Agents" ADD COLUMN "submissionDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "submissionReviewComments" TEXT, +ADD COLUMN "submissionReviewDate" TIMESTAMP(3), +ADD COLUMN "submissionStatus" "SubmissionStatus" NOT NULL DEFAULT 'PENDING'; diff --git a/autogpt_platform/market/migrations/20240829002156_make_featured_category_a_list_of_categories_and_is_featured_is_active/migration.sql b/autogpt_platform/market/migrations/20240829002156_make_featured_category_a_list_of_categories_and_is_featured_is_active/migration.sql new file mode 100644 index 000000000..3dc0ca322 --- /dev/null +++ b/autogpt_platform/market/migrations/20240829002156_make_featured_category_a_list_of_categories_and_is_featured_is_active/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `category` on the `FeaturedAgent` table. All the data in the column will be lost. + - You are about to drop the column `is_featured` on the `FeaturedAgent` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "FeaturedAgent" DROP COLUMN "category", +DROP COLUMN "is_featured", +ADD COLUMN "featuredCategories" TEXT[], +ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT false; diff --git a/autogpt_platform/market/migrations/20240905162237_/migration.sql b/autogpt_platform/market/migrations/20240905162237_/migration.sql new file mode 100644 index 000000000..ca2dc97bc --- /dev/null +++ b/autogpt_platform/market/migrations/20240905162237_/migration.sql @@ -0,0 +1,19 @@ +-- CreateEnum +CREATE TYPE "InstallationLocation" AS ENUM ('LOCAL', 'CLOUD'); + +-- CreateTable +CREATE TABLE "InstallTracker" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "marketplaceAgentId" UUID NOT NULL, + "installedAgentId" UUID NOT NULL, + "installationLocation" "InstallationLocation" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "InstallTracker_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "InstallTracker_marketplaceAgentId_installedAgentId_key" ON "InstallTracker"("marketplaceAgentId", "installedAgentId"); + +-- AddForeignKey +ALTER TABLE "InstallTracker" ADD CONSTRAINT "InstallTracker_marketplaceAgentId_fkey" FOREIGN KEY ("marketplaceAgentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/autogpt_platform/market/migrations/20241003134209_update_foreign_key_on_delete/migration.sql b/autogpt_platform/market/migrations/20241003134209_update_foreign_key_on_delete/migration.sql new file mode 100644 index 000000000..2e1bdc7b7 --- /dev/null +++ b/autogpt_platform/market/migrations/20241003134209_update_foreign_key_on_delete/migration.sql @@ -0,0 +1,20 @@ +-- DropForeignKey +ALTER TABLE "AnalyticsTracker" DROP CONSTRAINT "AnalyticsTracker_agentId_fkey"; + +-- DropForeignKey +ALTER TABLE "FeaturedAgent" DROP CONSTRAINT "FeaturedAgent_agentId_fkey"; + +-- DropForeignKey +ALTER TABLE "InstallTracker" DROP CONSTRAINT "InstallTracker_marketplaceAgentId_fkey"; + +-- DropIndex +DROP INDEX "AnalyticsTracker_agentId_key"; + +-- AddForeignKey +ALTER TABLE "AnalyticsTracker" ADD CONSTRAINT "AnalyticsTracker_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "InstallTracker" ADD CONSTRAINT "InstallTracker_marketplaceAgentId_fkey" FOREIGN KEY ("marketplaceAgentId") REFERENCES "Agents"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FeaturedAgent" ADD CONSTRAINT "FeaturedAgent_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/autogpt_platform/market/migrations/20241014173713_add_unique_restriction/migration.sql b/autogpt_platform/market/migrations/20241014173713_add_unique_restriction/migration.sql new file mode 100644 index 000000000..c1ce4b5de --- /dev/null +++ b/autogpt_platform/market/migrations/20241014173713_add_unique_restriction/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[agentId]` on the table `AnalyticsTracker` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "AnalyticsTracker_agentId_key" ON "AnalyticsTracker"("agentId"); diff --git a/autogpt_platform/market/migrations/migration_lock.toml b/autogpt_platform/market/migrations/migration_lock.toml new file mode 100644 index 000000000..fbffa92c2 --- /dev/null +++ b/autogpt_platform/market/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/autogpt_platform/market/poetry.lock b/autogpt_platform/market/poetry.lock new file mode 100644 index 000000000..f110bd3fb --- /dev/null +++ b/autogpt_platform/market/poetry.lock @@ -0,0 +1,1301 @@ +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "autogpt-libs" +version = "0.1.0" +description = "Shared libraries across NextGen AutoGPT" +optional = false +python-versions = ">=3.10,<4.0" +files = [] +develop = false + +[package.dependencies] +pyjwt = "^2.8.0" +python-dotenv = "^1.0.1" + +[package.source] +type = "directory" +url = "../autogpt_libs" + +[[package]] +name = "black" +version = "24.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.115.6" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, + {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.42.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fuzzywuzzy" +version = "0.18.0" +description = "Fuzzy string matching in python" +optional = false +python-versions = "*" +files = [ + {file = "fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"}, + {file = "fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8"}, +] + +[package.extras] +speedup = ["python-levenshtein (>=0.12)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "levenshtein" +version = "0.26.1" +description = "Python extension for computing string edit distances and similarities." +optional = false +python-versions = ">=3.9" +files = [ + {file = "levenshtein-0.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8dc4a4aecad538d944a1264c12769c99e3c0bf8e741fc5e454cc954913befb2e"}, + {file = "levenshtein-0.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec108f368c12b25787c8b1a4537a1452bc53861c3ee4abc810cc74098278edcd"}, + {file = "levenshtein-0.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69229d651c97ed5b55b7ce92481ed00635cdbb80fbfb282a22636e6945dc52d5"}, + {file = "levenshtein-0.26.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79dcd157046d62482a7719b08ba9e3ce9ed3fc5b015af8ea989c734c702aedd4"}, + {file = "levenshtein-0.26.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f53f9173ae21b650b4ed8aef1d0ad0c37821f367c221a982f4d2922b3044e0d"}, + {file = "levenshtein-0.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3956f3c5c229257dbeabe0b6aacd2c083ebcc1e335842a6ff2217fe6cc03b6b"}, + {file = "levenshtein-0.26.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1e83af732726987d2c4cd736f415dae8b966ba17b7a2239c8b7ffe70bfb5543"}, + {file = "levenshtein-0.26.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4f052c55046c2a9c9b5f742f39e02fa6e8db8039048b8c1c9e9fdd27c8a240a1"}, + {file = "levenshtein-0.26.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9895b3a98f6709e293615fde0dcd1bb0982364278fa2072361a1a31b3e388b7a"}, + {file = "levenshtein-0.26.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a3777de1d8bfca054465229beed23994f926311ce666f5a392c8859bb2722f16"}, + {file = "levenshtein-0.26.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:81c57e1135c38c5e6e3675b5e2077d8a8d3be32bf0a46c57276c092b1dffc697"}, + {file = "levenshtein-0.26.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:91d5e7d984891df3eff7ea9fec8cf06fdfacc03cd074fd1a410435706f73b079"}, + {file = "levenshtein-0.26.1-cp310-cp310-win32.whl", hash = "sha256:f48abff54054b4142ad03b323e80aa89b1d15cabc48ff49eb7a6ff7621829a56"}, + {file = "levenshtein-0.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:79dd6ad799784ea7b23edd56e3bf94b3ca866c4c6dee845658ee75bb4aefdabf"}, + {file = "levenshtein-0.26.1-cp310-cp310-win_arm64.whl", hash = "sha256:3351ddb105ef010cc2ce474894c5d213c83dddb7abb96400beaa4926b0b745bd"}, + {file = "levenshtein-0.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44c51f5d33b3cfb9db518b36f1288437a509edd82da94c4400f6a681758e0cb6"}, + {file = "levenshtein-0.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56b93203e725f9df660e2afe3d26ba07d71871b6d6e05b8b767e688e23dfb076"}, + {file = "levenshtein-0.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:270d36c5da04a0d89990660aea8542227cbd8f5bc34e9fdfadd34916ff904520"}, + {file = "levenshtein-0.26.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:480674c05077eeb0b0f748546d4fcbb386d7c737f9fff0010400da3e8b552942"}, + {file = "levenshtein-0.26.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13946e37323728695ba7a22f3345c2e907d23f4600bc700bf9b4352fb0c72a48"}, + {file = "levenshtein-0.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceb673f572d1d0dc9b1cd75792bb8bad2ae8eb78a7c6721e23a3867d318cb6f2"}, + {file = "levenshtein-0.26.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42d6fa242e3b310ce6bfd5af0c83e65ef10b608b885b3bb69863c01fb2fcff98"}, + {file = "levenshtein-0.26.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8b68295808893a81e0a1dbc2274c30dd90880f14d23078e8eb4325ee615fc68"}, + {file = "levenshtein-0.26.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b01061d377d1944eb67bc40bef5d4d2f762c6ab01598efd9297ce5d0047eb1b5"}, + {file = "levenshtein-0.26.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9d12c8390f156745e533d01b30773b9753e41d8bbf8bf9dac4b97628cdf16314"}, + {file = "levenshtein-0.26.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:48825c9f967f922061329d1481b70e9fee937fc68322d6979bc623f69f75bc91"}, + {file = "levenshtein-0.26.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8ec137170b95736842f99c0e7a9fd8f5641d0c1b63b08ce027198545d983e2b"}, + {file = "levenshtein-0.26.1-cp311-cp311-win32.whl", hash = "sha256:798f2b525a2e90562f1ba9da21010dde0d73730e277acaa5c52d2a6364fd3e2a"}, + {file = "levenshtein-0.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:55b1024516c59df55f1cf1a8651659a568f2c5929d863d3da1ce8893753153bd"}, + {file = "levenshtein-0.26.1-cp311-cp311-win_arm64.whl", hash = "sha256:e52575cbc6b9764ea138a6f82d73d3b1bc685fe62e207ff46a963d4c773799f6"}, + {file = "levenshtein-0.26.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc741ca406d3704dc331a69c04b061fc952509a069b79cab8287413f434684bd"}, + {file = "levenshtein-0.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:821ace3b4e1c2e02b43cf5dc61aac2ea43bdb39837ac890919c225a2c3f2fea4"}, + {file = "levenshtein-0.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92694c9396f55d4c91087efacf81297bef152893806fc54c289fc0254b45384"}, + {file = "levenshtein-0.26.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51ba374de7a1797d04a14a4f0ad3602d2d71fef4206bb20a6baaa6b6a502da58"}, + {file = "levenshtein-0.26.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7aa5c3327dda4ef952769bacec09c09ff5bf426e07fdc94478c37955681885b"}, + {file = "levenshtein-0.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e2517e8d3c221de2d1183f400aed64211fcfc77077b291ed9f3bb64f141cdc"}, + {file = "levenshtein-0.26.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9092b622765c7649dd1d8af0f43354723dd6f4e570ac079ffd90b41033957438"}, + {file = "levenshtein-0.26.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc16796c85d7d8b259881d59cc8b5e22e940901928c2ff6924b2c967924e8a0b"}, + {file = "levenshtein-0.26.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4370733967f5994ceeed8dc211089bedd45832ee688cecea17bfd35a9eb22b9"}, + {file = "levenshtein-0.26.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3535ecfd88c9b283976b5bc61265855f59bba361881e92ed2b5367b6990c93fe"}, + {file = "levenshtein-0.26.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:90236e93d98bdfd708883a6767826fafd976dac8af8fc4a0fb423d4fa08e1bf0"}, + {file = "levenshtein-0.26.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:04b7cabb82edf566b1579b3ed60aac0eec116655af75a3c551fee8754ffce2ea"}, + {file = "levenshtein-0.26.1-cp312-cp312-win32.whl", hash = "sha256:ae382af8c76f6d2a040c0d9ca978baf461702ceb3f79a0a3f6da8d596a484c5b"}, + {file = "levenshtein-0.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:fd091209798cfdce53746f5769987b4108fe941c54fb2e058c016ffc47872918"}, + {file = "levenshtein-0.26.1-cp312-cp312-win_arm64.whl", hash = "sha256:7e82f2ea44a81ad6b30d92a110e04cd3c8c7c6034b629aca30a3067fa174ae89"}, + {file = "levenshtein-0.26.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:790374a9f5d2cbdb30ee780403a62e59bef51453ac020668c1564d1e43438f0e"}, + {file = "levenshtein-0.26.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7b05c0415c386d00efda83d48db9db68edd02878d6dbc6df01194f12062be1bb"}, + {file = "levenshtein-0.26.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3114586032361722ddededf28401ce5baf1cf617f9f49fb86b8766a45a423ff"}, + {file = "levenshtein-0.26.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2532f8a13b68bf09f152d906f118a88da2063da22f44c90e904b142b0a53d534"}, + {file = "levenshtein-0.26.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:219c30be6aa734bf927188d1208b7d78d202a3eb017b1c5f01ab2034d2d4ccca"}, + {file = "levenshtein-0.26.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397e245e77f87836308bd56305bba630010cd8298c34c4c44bd94990cdb3b7b1"}, + {file = "levenshtein-0.26.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeff6ea3576f72e26901544c6c55c72a7b79b9983b6f913cba0e9edbf2f87a97"}, + {file = "levenshtein-0.26.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a19862e3539a697df722a08793994e334cd12791e8144851e8a1dee95a17ff63"}, + {file = "levenshtein-0.26.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:dc3b5a64f57c3c078d58b1e447f7d68cad7ae1b23abe689215d03fc434f8f176"}, + {file = "levenshtein-0.26.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bb6c7347424a91317c5e1b68041677e4c8ed3e7823b5bbaedb95bffb3c3497ea"}, + {file = "levenshtein-0.26.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b817376de4195a207cc0e4ca37754c0e1e1078c2a2d35a6ae502afde87212f9e"}, + {file = "levenshtein-0.26.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b50c3620ff47c9887debbb4c154aaaac3e46be7fc2e5789ee8dbe128bce6a17"}, + {file = "levenshtein-0.26.1-cp313-cp313-win32.whl", hash = "sha256:9fb859da90262eb474c190b3ca1e61dee83add022c676520f5c05fdd60df902a"}, + {file = "levenshtein-0.26.1-cp313-cp313-win_amd64.whl", hash = "sha256:8adcc90e3a5bfb0a463581d85e599d950fe3c2938ac6247b29388b64997f6e2d"}, + {file = "levenshtein-0.26.1-cp313-cp313-win_arm64.whl", hash = "sha256:c2599407e029865dc66d210b8804c7768cbdbf60f061d993bb488d5242b0b73e"}, + {file = "levenshtein-0.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc54ced948fc3feafce8ad4ba4239d8ffc733a0d70e40c0363ac2a7ab2b7251e"}, + {file = "levenshtein-0.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6516f69213ae393a220e904332f1a6bfc299ba22cf27a6520a1663a08eba0fb"}, + {file = "levenshtein-0.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4cfea4eada1746d0c75a864bc7e9e63d4a6e987c852d6cec8d9cb0c83afe25b"}, + {file = "levenshtein-0.26.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a323161dfeeac6800eb13cfe76a8194aec589cd948bcf1cdc03f66cc3ec26b72"}, + {file = "levenshtein-0.26.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c23e749b68ebc9a20b9047317b5cd2053b5856315bc8636037a8adcbb98bed1"}, + {file = "levenshtein-0.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f80dd7432d4b6cf493d012d22148db7af769017deb31273e43406b1fb7f091c"}, + {file = "levenshtein-0.26.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ae7cd6e4312c6ef34b2e273836d18f9fff518d84d823feff5ad7c49668256e0"}, + {file = "levenshtein-0.26.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdad740e841d791b805421c2b20e859b4ed556396d3063b3aa64cd055be648c"}, + {file = "levenshtein-0.26.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e07afb1613d6f5fd99abd4e53ad3b446b4efaa0f0d8e9dfb1d6d1b9f3f884d32"}, + {file = "levenshtein-0.26.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f1add8f1d83099a98ae4ac472d896b7e36db48c39d3db25adf12b373823cdeff"}, + {file = "levenshtein-0.26.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1010814b1d7a60833a951f2756dfc5c10b61d09976ce96a0edae8fecdfb0ea7c"}, + {file = "levenshtein-0.26.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:33fa329d1bb65ce85e83ceda281aea31cee9f2f6e167092cea54f922080bcc66"}, + {file = "levenshtein-0.26.1-cp39-cp39-win32.whl", hash = "sha256:488a945312f2f16460ab61df5b4beb1ea2254c521668fd142ce6298006296c98"}, + {file = "levenshtein-0.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:9f942104adfddd4b336c3997050121328c39479f69de702d7d144abb69ea7ab9"}, + {file = "levenshtein-0.26.1-cp39-cp39-win_arm64.whl", hash = "sha256:c1d8f85b2672939f85086ed75effcf768f6077516a3e299c2ba1f91bc4644c22"}, + {file = "levenshtein-0.26.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6cf8f1efaf90ca585640c5d418c30b7d66d9ac215cee114593957161f63acde0"}, + {file = "levenshtein-0.26.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d5b2953978b8c158dd5cd93af8216a5cfddbf9de66cf5481c2955f44bb20767a"}, + {file = "levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b952b3732c4631c49917d4b15d78cb4a2aa006c1d5c12e2a23ba8e18a307a055"}, + {file = "levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07227281e12071168e6ae59238918a56d2a0682e529f747b5431664f302c0b42"}, + {file = "levenshtein-0.26.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8191241cd8934feaf4d05d0cc0e5e72877cbb17c53bbf8c92af9f1aedaa247e9"}, + {file = "levenshtein-0.26.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9e70d7ee157a9b698c73014f6e2b160830e7d2d64d2e342fefc3079af3c356fc"}, + {file = "levenshtein-0.26.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0eb3059f826f6cb0a5bca4a85928070f01e8202e7ccafcba94453470f83e49d4"}, + {file = "levenshtein-0.26.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:6c389e44da12d6fb1d7ba0a709a32a96c9391e9be4160ccb9269f37e040599ee"}, + {file = "levenshtein-0.26.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e9de292f2c51a7d34a0ae23bec05391b8f61f35781cd3e4c6d0533e06250c55"}, + {file = "levenshtein-0.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d87215113259efdca8716e53b6d59ab6d6009e119d95d45eccc083148855f33"}, + {file = "levenshtein-0.26.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f00a3eebf68a82fb651d8d0e810c10bfaa60c555d21dde3ff81350c74fb4c2"}, + {file = "levenshtein-0.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b3554c1b59de63d05075577380340c185ff41b028e541c0888fddab3c259a2b4"}, + {file = "levenshtein-0.26.1.tar.gz", hash = "sha256:0d19ba22330d50609b2349021ec3cf7d905c6fe21195a2d0d876a146e7ed2575"}, +] + +[package.dependencies] +rapidfuzz = ">=3.9.0,<4.0.0" + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prisma" +version = "0.15.0" +description = "Prisma Client Python is an auto-generated and fully type-safe database client" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "prisma-0.15.0-py3-none-any.whl", hash = "sha256:de949cc94d3d91243615f22ff64490aa6e2d7cb81aabffce53d92bd3977c09a4"}, + {file = "prisma-0.15.0.tar.gz", hash = "sha256:5cd6402aa8322625db3fc1152040404e7fc471fe7f8fa3a314fa8a99529ca107"}, +] + +[package.dependencies] +click = ">=7.1.2" +httpx = ">=0.19.0" +jinja2 = ">=2.11.2" +nodeenv = "*" +pydantic = ">=1.10.0,<3" +python-dotenv = ">=0.12.0" +StrEnum = {version = "*", markers = "python_version < \"3.11\""} +tomlkit = "*" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["nodejs-bin"] +node = ["nodejs-bin"] + +[[package]] +name = "prometheus-client" +version = "0.20.0" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.8" +files = [ + {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"}, + {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prometheus-fastapi-instrumentator" +version = "7.0.0" +description = "Instrument your FastAPI with Prometheus metrics." +optional = false +python-versions = ">=3.8.1,<4.0.0" +files = [ + {file = "prometheus_fastapi_instrumentator-7.0.0-py3-none-any.whl", hash = "sha256:96030c43c776ee938a3dae58485ec24caed7e05bfc60fe067161e0d5b5757052"}, + {file = "prometheus_fastapi_instrumentator-7.0.0.tar.gz", hash = "sha256:5ba67c9212719f244ad7942d75ded80693b26331ee5dfc1e7571e4794a9ccbed"}, +] + +[package.dependencies] +prometheus-client = ">=0.8.0,<1.0.0" +starlette = ">=0.30.0,<1.0.0" + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pyjwt" +version = "2.9.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, + {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pyright" +version = "1.1.390" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.390-py3-none-any.whl", hash = "sha256:ecebfba5b6b50af7c1a44c2ba144ba2ab542c227eb49bc1f16984ff714e0e110"}, + {file = "pyright-1.1.390.tar.gz", hash = "sha256:aad7f160c49e0fbf8209507a15e17b781f63a86a1facb69ca877c71ef2e9538d"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" +typing-extensions = ">=4.1" + +[package.extras] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] + +[[package]] +name = "pytest" +version = "8.3.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.25.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, + {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-watcher" +version = "0.4.3" +description = "Automatically rerun your tests on file modifications" +optional = false +python-versions = "<4.0.0,>=3.7.0" +files = [ + {file = "pytest_watcher-0.4.3-py3-none-any.whl", hash = "sha256:d59b1e1396f33a65ea4949b713d6884637755d641646960056a90b267c3460f9"}, + {file = "pytest_watcher-0.4.3.tar.gz", hash = "sha256:0cb0e4661648c8c0ff2b2d25efa5a8e421784b9e4c60fcecbf9b7c30b2d731b3"}, +] + +[package.dependencies] +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +watchdog = ">=2.0.0" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-levenshtein" +version = "0.26.1" +description = "Python extension for computing string edit distances and similarities." +optional = false +python-versions = ">=3.9" +files = [ + {file = "python_Levenshtein-0.26.1-py3-none-any.whl", hash = "sha256:8ef5e529dd640fb00f05ee62d998d2ee862f19566b641ace775d5ae16167b2ef"}, + {file = "python_levenshtein-0.26.1.tar.gz", hash = "sha256:24ba578e28058ebb4afa2700057e1678d7adf27e43cd1f17700c09a9009d5d3a"}, +] + +[package.dependencies] +Levenshtein = "0.26.1" + +[[package]] +name = "rapidfuzz" +version = "3.9.5" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rapidfuzz-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7659058863d84a2c36c5a76c28bc8713d33eab03e677e67260d9e1cca43fc3bb"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:802a018776bd3cb7c5d23ba38ebbb1663a9f742be1d58e73b62d8c7cace6e607"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da71e8fdb0d1a21f4b58b2c84bcbc2b89a472c073c5f7bdb9339f4cb3122c0e3"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9433cb12731167b358fbcff9828d2294429986a03222031f6d14308eb643c77"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e33e1d185206730b916b3e7d9bce1941c65b2a1488cdd0457ae21be385a7912"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:758719e9613c47a274768f1926460955223fe0a03e7eda264f2b78b1b97a4743"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7981cc6240d01d4480795d758ea2ee748257771f68127d630045e58fe1b5545a"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b6cdca86120c3f9aa069f8d4e1c5422e92f833d705d719a2ba7082412f4c933b"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ffa533acb1a9dcb6e26c4467fdc1347995fb168ec9f794b97545f6b72dee733c"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:13eeaeb0d5fe00fa99336f73fb5ab65c46109c7121cf87659b9601908b8b6178"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d7b1922b1403ccb3583218e8cd931b08e04c5442ca03dbaf6ea4fcf574ee2b24"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b0189f691cea4dc9fe074ea6b97da30a91d0882fa69724b4b34b51d2c1983473"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-win32.whl", hash = "sha256:72e466e5de12a327a09ed6d0116f024759b5146b335645c32241da84134a7f34"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:345011cfcafaa3674c46673baad67d2394eb58285530d8333e65c3c9a143b4f4"}, + {file = "rapidfuzz-3.9.5-cp310-cp310-win_arm64.whl", hash = "sha256:5dc19c8222475e4f7f528b94d2fa28e7979355c5cf7c6e73902d2abb2be96522"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c741972d64031535cfd76d89cf47259e590e822353be57ec2f5d56758c98296"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7452d079800cf70a7314f73044f03cbcbd90a651d9dec39443d2a8a2b63ab53"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f06f163a0341bad162e972590b73e17f9cea2ed8ee27b193875ccbc3dd6eca2f"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:529e2cf441746bd492f6c36a38bf9fa6a418df95b9c003f8e92a55d8a979bd9c"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9811a741aa1350ad36689d675ded8b34e423e68b396bd30bff751a9c582f586e"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e36c4640a789b8c922b69a548968939d1c0433fa7aac83cb08e1334d4e5d7de"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53fb2f32f14c921d2f673c5b7cd58d4cc626c574a28c0791f283880d8e57022c"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:031806eb035a6f09f4ff23b9d971d50b30b5e93aa3ee620c920bee1dc32827e7"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f6dbe1df0b9334e3cf07445d810c81734ae23d137b5efc69e1d676ff55691351"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:24345826b50aafcea26e2e4be5c103d96fe9d7fc549ac9190641300290958f3b"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bfd3b66ee1f0ebb40c672a7a7e5bda00fb763fa9bca082058084175151f8e685"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6f1df5b0e602e94199cccb5e241bbc2319644003e34f077741ebf48aea7ed1a"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-win32.whl", hash = "sha256:f080d6709f51a8335e73826b96af9b4e3657631eca6c69e1ac501868dcc84b7f"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:bf9ed6988da6a2c1f8df367cb5d6be26a3d8543646c8eba79741ac9e764fbc59"}, + {file = "rapidfuzz-3.9.5-cp311-cp311-win_arm64.whl", hash = "sha256:599714790dfac0a23a473134e6677d0a103690a4e21ba189cfc826e322cdc8d5"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9729852038fb2de096e249899f8a9bee90fb1f92e10b6ccc539d5bb798c703bc"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9dc39435476fb3b3b3c24ab2c08c726056b2b487aa7ee450aee698b808c808ac"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6ceea632b0eb97dac54411c29feb190054e91fd0571f585b56e4a9159c55ab0"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cadd66e6ef9901909dc1b11db91048f1bf4613ba7d773386f922e28b1e1df4da"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63e34fb3586431589a5e1cd7fc61c6f057576c6c6804c1c673bac3de0516dee7"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:181073256faec68e6b8ab3329a36cfa1360f7906aa70d9aee4a39cb70889f73f"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8419c18bbbd67058ca1312f35acda2e4e4592650f105cfd166569a2ebccd01f1"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:191d1057cca56641f7b919fe712cb7e48cd226342e097a78136127f8bde32caa"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fe5a11eefd0ae90d32d9ff706a894498b4efb4b0c263ad9d1e6401050863504d"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b024d9d69bb83e125adee4162991f2764f16acc3fb1ed0f0fc1ad5aeb7e394"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d5a34b8388ae99bdbd5a3646f45ac318f4c870105bdbe42a2f4c85e5b347761"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0e09abc0d397019bba61c8e6dfe2ec863d4dfb1762f51c9197ce0af5d5fd9adb"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-win32.whl", hash = "sha256:e3c4be3057472c79ba6f4eab35daa9f12908cb697c472d05fbbd47949a87aec6"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:0d9fdb74df87018dd4146f3d00df9fca2c27f060936a9e8d3015e7bfb9cb69e4"}, + {file = "rapidfuzz-3.9.5-cp312-cp312-win_arm64.whl", hash = "sha256:491d3d425b5fe3f61f3b9a70abfd498ce9139d94956db7a8551e537e017c0e57"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:518dec750a30f115ba1299ef2547cf468a69f310581a030c8a875257de747c5f"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:252dc3d1c3d613b8db1b59d13381937e420c99f8a351ffa0e78c2f54746e107f"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd17688b75b6fa983e8586cad30f36eb9736b860946cc8b633b9442c9481831"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8032492021b0aa55a623d6f6e739a5d4aaabc32af379c2a5656bf1e9e178bf1"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73362eb1c3d02f32e4c7f0d77eb284e9a13f278cff224f71e8f60e2aff5b6a5d"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a42d1f7b8988f50013e703ed27b5e216ef8a725b2f4ac53754ad0476020b26f4"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4f2e985172bb76c9179e11fb67d9c9ecbee4933740eca2977797094df02498d"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e943c5cbd10e15369be1f371ef303cb413c1008f64d93bd13762ea06ca84d59"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:0d34b0e8e29f80cb2ac8afe8fb7b01a542b136ffbf7e2b9983d11bce49398f68"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:62b8f9f58e9dffaa86fef84db2705457a58e191a962124f2b815026ba79d9aba"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:ebf682bdb0f01b6b1f9a9ffe918aa3ac84fbdadb998ffbfcd5f9b12bd280170f"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3ed0c17e5b6fdd2ac3230bdefa908579971377c36aa4a2f132700fa8145040db"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-win32.whl", hash = "sha256:ac460d89b9759e37eef23fed05184179654882a241f6b2363df194f8940cc55f"}, + {file = "rapidfuzz-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:cf9aceb4227fd09f9a20e505f78487b2089d6420ce232d288522ea0a78b986b9"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14587df847d0d50bd10cde0a198b5d64eedb7484c72b825f5c2ead6e6ff16eee"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd94d952299ec73ea63a0fa4b699a2750785b6bb82aa56fd886d9023b86f90ab"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:733bf3d7876bf6d8167e6436f99d6ea16a218ec2c8eb9da6048f20b9cc8733e2"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb28f2b7173ed3678b4630b0c8b21503087d1cd082bae200dc2519ca38b26686"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a4c8a2c5ae4b133fec6b5db1af9a4126ffa6eca18a558fe5b6ab8e330d3d78"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5feb75e905281e5c669e21c98d594acc3b222a8694d9342f17df988766d83748"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d047b01637a31d9bf776b66438f574fd1db856ad14cf296c1f48bb6bef8a5aff"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d9e0a656274ac75ec24499a06c0bc5eee67bcd8276c6061da7c05d549f1b1a61"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:16c982dd3cdd33cf4aac91027a263a081d1a8050dc33a27470367a391a8d1576"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a0c878d0980508e90e973a9cbfb591acc370085f2301c6aacadbd8362d52a36"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1d9bcfec5efd55b6268328cccd12956d833582d8da6385231a5c6c6201a1156a"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8171fc6e4645e636161a9ef5b44b20605adbefe23cd990b68d72cae0b9c12509"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-win32.whl", hash = "sha256:35088e759b083398ab3c4154517476e116653b7403604677af9a894179f1042f"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:6d8cc7e6e5c6fbcacdfe3cf7a86b60dcaf216216d86e6879ff52d488e5b11e27"}, + {file = "rapidfuzz-3.9.5-cp39-cp39-win_arm64.whl", hash = "sha256:506547889f18db0acca787ffb9f287757cbfe9f0fadddd4e07c64ce0bd924e13"}, + {file = "rapidfuzz-3.9.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f4e0122603af2119579e9f94e172c6e460860fdcdb713164332c1951c13df999"}, + {file = "rapidfuzz-3.9.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e46cd486289d1d8e3dab779c725f5dde77b286185d32e7b874bfc3d161e3a927"}, + {file = "rapidfuzz-3.9.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e2c0c8bbe4f4525009e3ad9b94a39cdff5d6378233e754d0b13c29cdfaa75fc"}, + {file = "rapidfuzz-3.9.5-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb47513a17c935f6ee606dcae0ea9d20a3fb0fe9ca597758472ea08be62dc54"}, + {file = "rapidfuzz-3.9.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976ed1105a76935b6a4d2bbc7d577be1b97b43997bcec2f29a0ab48ff6f5d6b1"}, + {file = "rapidfuzz-3.9.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9cf2028edb9ccd21d1d5aaacef2fe3e14bee4343df1c2c0f7373ef6e81013bef"}, + {file = "rapidfuzz-3.9.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:926701c8e61319ee2e4888619143f58ddcc0e3e886668269b8e053f2d68c1e92"}, + {file = "rapidfuzz-3.9.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:99eaa8dd8a44664813e0bef014775993ef75a134a863bc54cd855a60622203fd"}, + {file = "rapidfuzz-3.9.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7508ef727ef4891141dd3ac7a39a2327384ece070521ac9c58f06c27d57c72d5"}, + {file = "rapidfuzz-3.9.5-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f33d05db5bba1d076446c51347a6d93ff24d8f9d01b0b8b15ca8ec8b1ef382"}, + {file = "rapidfuzz-3.9.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7252666b85c931d51a59d5308bb6827a67434917ef510747d3ce7e88ec17e7f2"}, + {file = "rapidfuzz-3.9.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d26f7299e2872d18fb7df1bc043e53aa94fc5a4a2a6a9537ad8707579fcb1668"}, + {file = "rapidfuzz-3.9.5-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2b17ecc17322b659962234799e90054e420911b8ca510a7869c2f4419f9f3ecb"}, + {file = "rapidfuzz-3.9.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f3e037b9ec621dec0157d81566e7d47a91405e379335cf8f4ed3c20d61db91d8"}, + {file = "rapidfuzz-3.9.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c4d1ba2647c8d2a82313c4dde332de750c936b94f016308339e762c2e5e53d"}, + {file = "rapidfuzz-3.9.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:876e663b11d9067e1096ea76a2de87227c7b513aff2b60667b20417da74183e4"}, + {file = "rapidfuzz-3.9.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adee55488490375c1604b878fbc1eb1a51fe5e6f5bd05047df2f8c6505a48728"}, + {file = "rapidfuzz-3.9.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:abb1ac683671000bd4ec215a494aba687d75a198db72188408154a19ea313ff4"}, + {file = "rapidfuzz-3.9.5.tar.gz", hash = "sha256:257f2406a671371bafd99a2a2c57f991783446bc2176b93a83d1d833e35d36df"}, +] + +[package.extras] +full = ["numpy"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.8.3" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, + {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, + {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, + {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, + {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, + {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, + {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, +] + +[[package]] +name = "sentry-sdk" +version = "2.19.2" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"}, + {file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"}, +] + +[package.dependencies] +certifi = "*" +fastapi = {version = ">=0.79.0", optional = true, markers = "extra == \"fastapi\""} +urllib3 = ">=1.26.11" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] +http2 = ["httpcore[http2] (==1.*)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +huggingface-hub = ["huggingface_hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"] +litestar = ["litestar (>=2.0.0)"] +loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +openfeature = ["openfeature-sdk (>=0.7.1)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro"] +pure-eval = ["asttokens", "executing", "pure_eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=6)"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.41.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, + {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "strenum" +version = "0.4.15" +description = "An Enum that inherits from str." +optional = false +python-versions = "*" +files = [ + {file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"}, + {file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"}, +] + +[package.extras] +docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"] +release = ["twine"] +test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.34.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.9" +files = [ + {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, + {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "watchdog" +version = "4.0.1" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "5dbf6cd95ba8e80c4a6b4e6a54c6cdfb1488619e4293d1d5a8572c5330485493" diff --git a/autogpt_platform/market/pyproject.toml b/autogpt_platform/market/pyproject.toml new file mode 100644 index 000000000..279a11116 --- /dev/null +++ b/autogpt_platform/market/pyproject.toml @@ -0,0 +1,57 @@ +[tool.poetry] +name = "market" +version = "0.1.0" +description = "" +authors = [ + "SwiftyOS ", + "Nicholas Tindle ", +] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +prisma = "^0.15.0" +python-dotenv = "^1.0.1" +uvicorn = "^0.34.0" +fastapi = "^0.115.6" +sentry-sdk = { extras = ["fastapi"], version = "^2.19.2" } +fuzzywuzzy = "^0.18.0" +python-levenshtein = "^0.26.1" +# autogpt-platform-backend = { path = "../backend", develop = true } +prometheus-fastapi-instrumentator = "^7.0.0" + + +autogpt-libs = {path = "../autogpt_libs"} +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.4" +pytest-asyncio = "^0.25.0" + +pytest-watcher = "^0.4.3" +requests = "^2.32.3" +ruff = "^0.8.3" +pyright = "^1.1.390" +isort = "^5.13.2" +black = "^24.10.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +format = "scripts:format" +lint = "scripts:lint" +app = "scripts:app" +setup = "scripts:setup" +populate = "scripts:populate_database" + +[tool.pytest-watcher] +now = false +clear = true +delay = 0.2 +runner = "pytest" +runner_args = [] +patterns = ["*.py"] +ignore_patterns = [] + +[tool.pytest.ini_options] +asyncio_mode = "auto" diff --git a/autogpt_platform/market/schema.prisma b/autogpt_platform/market/schema.prisma new file mode 100644 index 000000000..8a29c2b2a --- /dev/null +++ b/autogpt_platform/market/schema.prisma @@ -0,0 +1,80 @@ +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-py" + recursive_type_depth = 5 + interface = "asyncio" + previewFeatures = ["fullTextSearch"] + partial_type_generator = "market/utils/partial_types.py" +} + +enum SubmissionStatus { + PENDING + APPROVED + REJECTED +} + +model Agents { + id String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + version Int @default(1) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + submissionDate DateTime @default(now()) + submissionReviewDate DateTime? + submissionStatus SubmissionStatus @default(PENDING) + submissionReviewComments String? + + name String? + description String? + author String? + + keywords String[] + categories String[] + search Unsupported("tsvector")? @default(dbgenerated("''::tsvector")) + + graph Json + AnalyticsTracker AnalyticsTracker[] + FeaturedAgent FeaturedAgent? + InstallTracker InstallTracker[] + + @@id(name: "graphVersionId", [id, version]) +} + +model AnalyticsTracker { + id String @id @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + agentId String @unique @db.Uuid + agent Agents @relation(fields: [agentId], references: [id], onDelete: Cascade) + views Int + downloads Int +} + +enum InstallationLocation { + LOCAL + CLOUD +} + +model InstallTracker { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + marketplaceAgentId String @db.Uuid + marketplaceAgent Agents @relation(fields: [marketplaceAgentId], references: [id], onDelete: Cascade) + installedAgentId String @db.Uuid + installationLocation InstallationLocation + createdAt DateTime @default(now()) + + @@unique([marketplaceAgentId, installedAgentId]) +} + +model FeaturedAgent { + id String @id @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + agentId String @unique @db.Uuid + agent Agents @relation(fields: [agentId], references: [id], onDelete: Cascade) + isActive Boolean @default(false) + featuredCategories String[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/autogpt_platform/market/scripts.py b/autogpt_platform/market/scripts.py new file mode 100644 index 000000000..5465007b7 --- /dev/null +++ b/autogpt_platform/market/scripts.py @@ -0,0 +1,65 @@ +import os +import subprocess + +directory = os.path.dirname(os.path.realpath(__file__)) + + +def run(*command: str) -> None: + print(f">>>>> Running poetry run {' '.join(command)}") + subprocess.run(["poetry", "run"] + list(command), cwd=directory, check=True) + + +def lint(): + try: + run("ruff", "check", ".", "--exit-zero") + run("isort", "--diff", "--check", "--profile", "black", ".") + run("black", "--diff", "--check", ".") + run("pyright") + except subprocess.CalledProcessError as e: + print("Lint failed, try running `poetry run format` to fix the issues: ", e) + raise e + + +def populate_database(): + import glob + import json + import pathlib + + import requests + + import market.model + + templates = pathlib.Path(__file__).parent.parent / "graph_templates" + + all_files = glob.glob(str(templates / "*.json")) + + for file in all_files: + with open(file, "r") as f: + data = f.read() + req = market.model.AddAgentRequest( + graph=json.loads(data), + author="Populate DB", + categories=["Pre-Populated"], + keywords=["test"], + ) + response = requests.post( + "http://localhost:8015/api/v1/market/admin/agent", json=req.model_dump() + ) + print(response.text) + + +def format(): + run("ruff", "check", "--fix", ".") + run("isort", "--profile", "black", ".") + run("black", ".") + run("pyright", ".") + + +def app(): + port = os.getenv("PORT", "8015") + run("uvicorn", "market.app:app", "--reload", "--port", port, "--host", "0.0.0.0") + + +def setup(): + run("prisma", "generate") + run("prisma", "migrate", "deploy") diff --git a/autogpt_platform/market/tests/test_agents.py b/autogpt_platform/market/tests/test_agents.py new file mode 100644 index 000000000..bcf9f033f --- /dev/null +++ b/autogpt_platform/market/tests/test_agents.py @@ -0,0 +1,79 @@ +from datetime import datetime, timezone + +import pytest +from fastapi.testclient import TestClient + +from market.app import app +from market.db import AgentQueryError + + +@pytest.fixture +def test_client(): + return TestClient(app) + + +# Mock data +mock_agents = [ + { + "id": "1", + "name": "Agent 1", + "description": "Description 1", + "author": "Author 1", + "keywords": ["AI", "chatbot"], + "categories": ["general"], + "version": 1, + "createdAt": datetime.now(timezone.utc), + "updatedAt": datetime.now(timezone.utc), + "graph": {"node1": "value1"}, + }, + { + "id": "2", + "name": "Agent 2", + "description": "Description 2", + "author": "Author 2", + "keywords": ["ML", "NLP"], + "categories": ["specialized"], + "version": 1, + "createdAt": datetime.now(timezone.utc), + "updatedAt": datetime.now(timezone.utc), + "graph": {"node2": "value2"}, + }, +] + + +# TODO: Need to mock prisma somehow + + +@pytest.mark.asyncio +async def test_list_agents(test_client): + response = test_client.get("/agents") + assert response.status_code == 200 + data = response.json() + assert len(data["agents"]) == 2 + assert data["total_count"] == 2 + + +@pytest.mark.asyncio +async def test_list_agents_with_filters(test_client): + response = await test_client.get("/agents?name=Agent 1&keyword=AI&category=general") + assert response.status_code == 200 + data = response.json() + assert len(data["agents"]) == 1 + assert data["agents"][0]["name"] == "Agent 1" + + +@pytest.mark.asyncio +async def test_get_agent_details(test_client, mock_get_agent_details): + response = await test_client.get("/agents/1") + assert response.status_code == 200 + data = response.json() + assert data["id"] == "1" + assert data["name"] == "Agent 1" + assert "graph" in data + + +@pytest.mark.asyncio +async def test_get_nonexistent_agent(test_client, mock_get_agent_details): + mock_get_agent_details.side_effect = AgentQueryError("Agent not found") + response = await test_client.get("/agents/999") + assert response.status_code == 404