diff --git a/.vscode/all-projects.code-workspace b/.vscode/all-projects.code-workspace index 3b29dd69f..b76dec6b4 100644 --- a/.vscode/all-projects.code-workspace +++ b/.vscode/all-projects.code-workspace @@ -28,13 +28,17 @@ "name": "autogpt_builder", "path": "../rnd/autogpt_builder" }, + { + "name": "market", + "path": "../rnd/market" + }, { "name": "[root]", "path": ".." } ], "settings": { - "python.analysis.typeCheckingMode": "basic", + "python.analysis.typeCheckingMode": "basic" }, "extensions": { "recommendations": [ diff --git a/rnd/autogpt_builder/.env.example b/rnd/autogpt_builder/.env.example index e494243c6..0eeef7c2e 100644 --- a/rnd/autogpt_builder/.env.example +++ b/rnd/autogpt_builder/.env.example @@ -1,4 +1,5 @@ -AGPT_SERVER_URL=http://localhost:8000/api +NEXT_PUBLIC_AGPT_SERVER_URL=http://localhost:8000/api +NEXT_PUBLIC_AGPT_MARKETPLACE_URL=http://localhost:8001/api/v1/market ## Supabase credentials ## YOU ONLY NEED THEM IF YOU WANT TO USE SUPABASE USER AUTHENTICATION diff --git a/rnd/autogpt_builder/next.config.mjs b/rnd/autogpt_builder/next.config.mjs index 5c47b636d..44c0dbe5e 100644 --- a/rnd/autogpt_builder/next.config.mjs +++ b/rnd/autogpt_builder/next.config.mjs @@ -6,7 +6,9 @@ dotenv.config(); /** @type {import('next').NextConfig} */ const nextConfig = { env: { - AGPT_SERVER_URL: process.env.AGPT_SERVER_URL, + NEXT_PUBLIC_AGPT_SERVER_URL: process.env.NEXT_PUBLIC_AGPT_SERVER_URL, + NEXT_PUBLIC_AGPT_MARKETPLACE_URL: + process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL, }, async redirects() { return [ diff --git a/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx b/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx new file mode 100644 index 000000000..84e17d85c --- /dev/null +++ b/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx @@ -0,0 +1,41 @@ +import { Suspense } from "react"; +import { notFound } from "next/navigation"; +import MarketplaceAPI from "@/lib/marketplace-api"; +import { AgentDetailResponse } from "@/lib/marketplace-api"; +import AgentDetailContent from "@/components/AgentDetailContent"; + +async function getAgentDetails(id: string): Promise { + const apiUrl = + process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL || + "http://localhost:8001/api/v1/market"; + const api = new MarketplaceAPI(apiUrl); + try { + console.log(`Fetching agent details for id: ${id}`); + const agent = await api.getAgentDetails(id); + console.log(`Agent details fetched:`, agent); + return agent; + } catch (error) { + console.error(`Error fetching agent details:`, error); + throw error; + } +} + +export default async function AgentDetailPage({ + params, +}: { + params: { id: string }; +}) { + let agent: AgentDetailResponse; + + try { + agent = await getAgentDetails(params.id); + } catch (error) { + return notFound(); + } + + return ( + Loading...}> + + + ); +} diff --git a/rnd/autogpt_builder/src/app/marketplace/page.tsx b/rnd/autogpt_builder/src/app/marketplace/page.tsx new file mode 100644 index 000000000..eddc17dae --- /dev/null +++ b/rnd/autogpt_builder/src/app/marketplace/page.tsx @@ -0,0 +1,293 @@ +"use client"; +import React, { useEffect, useMemo, useState, useCallback } from "react"; +import { useRouter } from "next/navigation"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import MarketplaceAPI, { + AgentResponse, + AgentListResponse, + AgentWithRank, +} from "@/lib/marketplace-api"; +import { ChevronLeft, ChevronRight, Search, Star } from "lucide-react"; + +// Utility Functions +function debounce any>( + func: T, + wait: number, +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null; + return (...args: Parameters) => { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +} + +// Types +type Agent = AgentResponse | AgentWithRank; + +// Components +const HeroSection: React.FC = () => ( +
+
+ Marketplace background + +
+
+

+ AutoGPT Marketplace +

+

+ Discover and share proven AI Agents to supercharge your business. +

+
+
+); + +const SearchInput: React.FC<{ + value: string; + onChange: (e: React.ChangeEvent) => void; +}> = ({ value, onChange }) => ( +
+ + +
+); + +const AgentCard: React.FC<{ agent: Agent; featured?: boolean }> = ({ + agent, + featured = false, +}) => { + const router = useRouter(); + + const handleClick = () => { + router.push(`/marketplace/${agent.id}`); + }; + + return ( +
+
+
+

+ {agent.name} +

+ {featured && } +
+

+ {agent.description} +

+
+ Categories: {agent.categories.join(", ")} +
+
+
+
+ Updated {new Date(agent.updatedAt).toLocaleDateString()} +
+
Downloads {agent.downloads}
+ {"rank" in agent && ( +
+ Rank: {agent.rank.toFixed(2)} +
+ )} +
+
+ ); +}; + +const AgentGrid: React.FC<{ + agents: Agent[]; + title: string; + featured?: boolean; +}> = ({ agents, title, featured = false }) => ( +
+

{title}

+
+ {agents.map((agent) => ( + + ))} +
+
+); + +const Pagination: React.FC<{ + page: number; + totalPages: number; + onPrevPage: () => void; + onNextPage: () => void; +}> = ({ page, totalPages, onPrevPage, onNextPage }) => ( +
+ + + Page {page} of {totalPages} + + +
+); + +// Main Component +const Marketplace: React.FC = () => { + const apiUrl = + process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL || + "http://localhost:8001/api/v1/market"; + const api = useMemo(() => new MarketplaceAPI(apiUrl), [apiUrl]); + + const [searchValue, setSearchValue] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const [featuredAgents, setFeaturedAgents] = useState([]); + const [topAgents, setTopAgents] = useState([]); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(false); + + const fetchTopAgents = useCallback( + async (currentPage: number) => { + setIsLoading(true); + try { + const response = await api.getTopDownloadedAgents(currentPage, 9); + setTopAgents(response.agents); + setTotalPages(response.total_pages); + } catch (error) { + console.error("Error fetching top agents:", error); + } finally { + setIsLoading(false); + } + }, + [api], + ); + + const fetchFeaturedAgents = useCallback(async () => { + try { + const featured = await api.getFeaturedAgents(); + setFeaturedAgents(featured.agents); + } catch (error) { + console.error("Error fetching featured agents:", error); + } + }, [api]); + + const searchAgents = useCallback( + async (searchTerm: string) => { + setIsLoading(true); + try { + const response = await api.searchAgents(searchTerm, 1, 30); + const filteredAgents = response.filter((agent) => agent.rank > 0); + setSearchResults(filteredAgents); + } catch (error) { + console.error("Error searching agents:", error); + } finally { + setIsLoading(false); + } + }, + [api], + ); + + const debouncedSearch = useMemo( + () => debounce(searchAgents, 300), + [searchAgents], + ); + + useEffect(() => { + if (searchValue) { + debouncedSearch(searchValue); + } else { + fetchTopAgents(page); + } + }, [searchValue, page, debouncedSearch, fetchTopAgents]); + + useEffect(() => { + fetchFeaturedAgents(); + }, [fetchFeaturedAgents]); + + const handleInputChange = (e: React.ChangeEvent) => { + setSearchValue(e.target.value); + setPage(1); + }; + + const handleNextPage = () => { + if (page < totalPages) { + setPage(page + 1); + } + }; + + const handlePrevPage = () => { + if (page > 1) { + setPage(page - 1); + } + }; + + return ( +
+ +
+ + {isLoading ? ( +
+
+

Loading agents...

+
+ ) : searchValue ? ( + searchResults.length > 0 ? ( + + ) : ( +
+

+ No agents found matching your search criteria. +

+
+ ) + ) : ( + <> + {featuredAgents.length > 0 && ( + + )} + + + + )} +
+
+ ); +}; + +export default Marketplace; diff --git a/rnd/autogpt_builder/src/components/AgentDetailContent.tsx b/rnd/autogpt_builder/src/components/AgentDetailContent.tsx new file mode 100644 index 000000000..1902985d5 --- /dev/null +++ b/rnd/autogpt_builder/src/components/AgentDetailContent.tsx @@ -0,0 +1,203 @@ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; +import { + ArrowLeft, + Download, + Calendar, + Tag, + ChevronDown, + ChevronUp, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { AgentDetailResponse } from "@/lib/marketplace-api"; +import dynamic from "next/dynamic"; +import { Node, Edge, NodeTypes, EdgeTypes } from "reactflow"; +import MarketplaceAPI from "@/lib/marketplace-api"; +import AutoGPTServerAPI, { GraphCreatable } from "@/lib/autogpt-server-api"; + +const ReactFlow = dynamic( + () => import("reactflow").then((mod) => mod.default), + { ssr: false }, +); +const Controls = dynamic( + () => import("reactflow").then((mod) => mod.Controls), + { ssr: false }, +); +const Background = dynamic( + () => import("reactflow").then((mod) => mod.Background), + { ssr: false }, +); + +import "reactflow/dist/style.css"; +import CustomNode from "./CustomNode"; +import { CustomEdge } from "./CustomEdge"; +import ConnectionLine from "./ConnectionLine"; +import { beautifyString } from "@/lib/utils"; + +function convertGraphToReactFlow(graph: any): { nodes: Node[]; edges: Edge[] } { + const nodes: Node[] = graph.nodes.map((node: any) => { + let label = node.block_id || "Unknown"; + try { + label = beautifyString(label); + } catch (error) { + console.error("Error beautifying node label:", error); + } + + return { + id: node.id, + position: node.metadata.position || { x: 0, y: 0 }, + data: { + label, + blockId: node.block_id, + inputDefault: node.input_default || {}, + ...node, // Include all other node data + }, + type: "custom", + }; + }); + + const edges: Edge[] = graph.links.map((link: any) => ({ + id: `${link.source_id}-${link.sink_id}`, + source: link.source_id, + target: link.sink_id, + sourceHandle: link.source_name, + targetHandle: link.sink_name, + type: "custom", + data: { + sourceId: link.source_id, + targetId: link.sink_id, + sourceName: link.source_name, + targetName: link.sink_name, + isStatic: link.is_static, + }, + })); + + return { nodes, edges }; +} + +async function installGraph(id: string): Promise { + const apiUrl = + process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL || + "http://localhost:8001/api/v1/market"; + const api = new MarketplaceAPI(apiUrl); + + const serverAPIUrl = process.env.AGPT_SERVER_API_URL; + const serverAPI = new AutoGPTServerAPI(serverAPIUrl); + try { + console.log(`Installing agent with id: ${id}`); + let agent = await api.downloadAgent(id); + console.log(`Agent downloaded:`, agent); + const data: GraphCreatable = { + id: agent.id, + version: agent.version, + is_active: true, + is_template: false, + name: agent.name, + description: agent.description, + nodes: agent.graph.nodes, + links: agent.graph.links, + }; + await serverAPI.createTemplate(data); + console.log(`Agent installed successfully`); + } catch (error) { + console.error(`Error installing agent:`, error); + throw error; + } +} + +function AgentDetailContent({ agent }: { agent: AgentDetailResponse }) { + const [isGraphExpanded, setIsGraphExpanded] = useState(false); + const { nodes, edges } = convertGraphToReactFlow(agent.graph); + + const nodeTypes: NodeTypes = useMemo(() => ({ custom: CustomNode }), []); + const edgeTypes: EdgeTypes = useMemo(() => ({ custom: CustomEdge }), []); + + return ( +
+
+ + + Back to Marketplace + + +
+
+
+

{agent.name}

+

+ {agent.description} +

+
+
+
+
+
+ + Last Updated +
+
+ {new Date(agent.updatedAt).toLocaleDateString()} +
+
+
+
+ + Categories +
+
+ {agent.categories.join(", ")} +
+
+
+
+
+ + {isGraphExpanded && ( +
+ + + + +
+ )} +
+
+
+ ); +} + +export default AgentDetailContent; diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index 8bc7f5a7c..9441960c7 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -64,7 +64,7 @@ const FlowEditor: React.FC<{ const [copiedEdges, setCopiedEdges] = useState[]>([]); const [isAnyModalOpen, setIsAnyModalOpen] = useState(false); // Track if any modal is open - const apiUrl = process.env.AGPT_SERVER_URL!; + const apiUrl = process.env.NEXT_PUBLIC_AGPT_SERVER_URL!; const api = useMemo(() => new AutoGPTServerAPI(apiUrl), [apiUrl]); const initialPositionRef = useRef<{ [key: string]: { x: number; y: number }; diff --git a/rnd/autogpt_builder/src/components/NavBar.tsx b/rnd/autogpt_builder/src/components/NavBar.tsx index 6a7861e04..580ac446c 100644 --- a/rnd/autogpt_builder/src/components/NavBar.tsx +++ b/rnd/autogpt_builder/src/components/NavBar.tsx @@ -9,7 +9,7 @@ import { CircleUser, Menu, SquareActivity, Workflow } from "lucide-react"; import { Button, buttonVariants } from "@/components/ui/button"; import React from "react"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; -import { Pencil1Icon, TimerIcon } from "@radix-ui/react-icons"; +import { Pencil1Icon, TimerIcon, ArchiveIcon } from "@radix-ui/react-icons"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import Image from "next/image"; import getServerUser from "@/hooks/getServerUser"; @@ -23,7 +23,7 @@ export async function NavBar() { const { user } = await getServerUser(); return ( -
+
@@ -50,6 +50,12 @@ export async function NavBar() { > Build + + Marketplace + @@ -66,6 +72,12 @@ export async function NavBar() { > Build + + Marketplace +
diff --git a/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts b/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts index 2e494e221..b2f419280 100644 --- a/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts +++ b/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts @@ -15,7 +15,7 @@ export default class AutoGPTServerAPI { private messageHandlers: { [key: string]: (data: any) => void } = {}; constructor( - baseUrl: string = process.env.AGPT_SERVER_URL || + baseUrl: string = process.env.NEXT_PUBLIC_AGPT_SERVER_URL || "http://localhost:8000/api", ) { this.baseUrl = baseUrl; diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/client.ts b/rnd/autogpt_builder/src/lib/marketplace-api/client.ts new file mode 100644 index 000000000..cc5240d59 --- /dev/null +++ b/rnd/autogpt_builder/src/lib/marketplace-api/client.ts @@ -0,0 +1,167 @@ +import { + AddAgentRequest, + AgentResponse, + ListAgentsParams, + AgentListResponse, + AgentDetailResponse, + AgentWithRank, +} from "./types"; + +export default class MarketplaceAPI { + private baseUrl: string; + + constructor( + baseUrl: string = process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL || + "http://localhost:8001/api/v1/market", + ) { + this.baseUrl = baseUrl; + } + + async checkHealth(): Promise<{ status: string }> { + try { + this._get("/health"); + return { status: "available" }; + } catch (error) { + return { status: "unavailable" }; + } + } + + async listAgents(params: ListAgentsParams = {}): Promise { + const queryParams = new URLSearchParams( + Object.entries(params).filter(([_, v]) => v != null) as [ + string, + string, + ][], + ); + return this._get(`/agents?${queryParams.toString()}`); + } + + async getTopDownloadedAgents( + page: number = 1, + pageSize: number = 10, + ): Promise { + return this._get( + `/top-downloads/agents?page=${page}&page_size=${pageSize}`, + ); + } + + async getFeaturedAgents( + page: number = 1, + pageSize: number = 10, + ): Promise { + return this._get(`/featured/agents?page=${page}&page_size=${pageSize}`); + } + + async searchAgents( + query: string, + page: number = 1, + pageSize: number = 10, + categories?: string[], + descriptionThreshold: number = 60, + sortBy: string = "rank", + sortOrder: "asc" | "desc" = "desc", + ): Promise { + const queryParams = new URLSearchParams({ + query, + page: page.toString(), + page_size: pageSize.toString(), + description_threshold: descriptionThreshold.toString(), + sort_by: sortBy, + sort_order: sortOrder, + }); + + if (categories && categories.length > 0) { + categories.forEach((category) => + queryParams.append("categories", category), + ); + } + + return this._get(`/search?${queryParams.toString()}`); + } + + async getAgentDetails( + id: string, + version?: number, + ): Promise { + const queryParams = new URLSearchParams(); + if (version) queryParams.append("version", version.toString()); + return this._get(`/agents/${id}?${queryParams.toString()}`); + } + + async downloadAgent( + id: string, + version?: number, + ): Promise { + const queryParams = new URLSearchParams(); + if (version) queryParams.append("version", version.toString()); + return this._get(`/agents/${id}/download?${queryParams.toString()}`); + } + + async downloadAgentFile(id: string, version?: number): Promise { + const queryParams = new URLSearchParams(); + if (version) queryParams.append("version", version.toString()); + return this._getBlob( + `/agents/${id}/download-file?${queryParams.toString()}`, + ); + } + + async createAgentEntry(request: AddAgentRequest): Promise { + return this._post("/admin/agent", request); + } + + private async _get(path: string) { + return this._request("GET", path); + } + + private async _post(path: string, payload: { [key: string]: any }) { + return this._request("POST", path, payload); + } + + private async _getBlob(path: string): Promise { + const response = await fetch(this.baseUrl + path); + if (!response.ok) { + const errorData = await response.json(); + console.warn( + `GET ${path} returned non-OK response:`, + errorData.detail, + response, + ); + throw new Error(`HTTP error ${response.status}! ${errorData.detail}`); + } + return response.blob(); + } + + private async _request( + method: "GET" | "POST" | "PUT" | "PATCH", + path: string, + payload?: { [key: string]: any }, + ) { + if (method != "GET") { + console.debug(`${method} ${path} payload:`, payload); + } + + const response = await fetch( + this.baseUrl + path, + method != "GET" + ? { + method, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + } + : undefined, + ); + const response_data = await response.json(); + + if (!response.ok) { + console.warn( + `${method} ${path} returned non-OK response:`, + response_data.detail, + response, + ); + throw new Error(`HTTP error ${response.status}! ${response_data.detail}`); + } + return response_data; + } +} diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/index.ts b/rnd/autogpt_builder/src/lib/marketplace-api/index.ts new file mode 100644 index 000000000..f401d62bf --- /dev/null +++ b/rnd/autogpt_builder/src/lib/marketplace-api/index.ts @@ -0,0 +1,4 @@ +import MarketplaceAPI from "./client"; + +export default MarketplaceAPI; +export * from "./types"; diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/types.ts b/rnd/autogpt_builder/src/lib/marketplace-api/types.ts new file mode 100644 index 000000000..3ecc250e0 --- /dev/null +++ b/rnd/autogpt_builder/src/lib/marketplace-api/types.ts @@ -0,0 +1,58 @@ +export type ListAgentsParams = { + page?: number; + page_size?: number; + name?: string; + keyword?: string; + category?: string; + description?: string; + description_threshold?: number; + sort_by?: string; + sort_order?: "asc" | "desc"; +}; + +export type AddAgentRequest = { + graph: { + name: string; + description: string; + [key: string]: any; + }; + author: string; + keywords: string[]; + categories: string[]; +}; + +export type Agent = { + id: string; + name: string; + description: string; + author: string; + keywords: string[]; + categories: string[]; + version: number; + createdAt: string; // ISO8601 datetime string + updatedAt: string; // ISO8601 datetime string + views: number; + downloads: number; +}; + +export type AgentList = { + agents: Agent[]; + total_count: number; + page: number; + page_size: number; + total_pages: number; +}; + +export type AgentDetail = Agent & { + graph: Record; +}; + +export type AgentWithRank = Agent & { + rank: number; +}; + +export type AgentListResponse = AgentList; + +export type AgentDetailResponse = AgentDetail; + +export type AgentResponse = Agent; diff --git a/rnd/autogpt_server/autogpt_server/server/server.py b/rnd/autogpt_server/autogpt_server/server/server.py index bc4d5ecaf..2fee63832 100644 --- a/rnd/autogpt_server/autogpt_server/server/server.py +++ b/rnd/autogpt_server/autogpt_server/server/server.py @@ -1,4 +1,5 @@ import asyncio +import uuid from collections import defaultdict from contextlib import asynccontextmanager from typing import Annotated, Any, Dict diff --git a/rnd/market/.env.example b/rnd/market/.env.example index 3d982c3e5..2187e8cc4 100644 --- a/rnd/market/.env.example +++ b/rnd/market/.env.example @@ -4,4 +4,4 @@ DB_PASS=pass123 DB_NAME=marketplace DB_PORT=5432 DATABASE_URL=postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/${DB_NAME} -SENTRY_DSN=https://sentry.io \ No newline at end of file +SENTRY_DSN=Set correct url or dealete me \ No newline at end of file diff --git a/rnd/market/.vscode/launch.json b/rnd/market/.vscode/launch.json new file mode 100644 index 000000000..96ed63cc6 --- /dev/null +++ b/rnd/market/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Market - FastAPI", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": ["market.app:app", "--reload", "--port", "8001"], + "jinja": true + } + ] +} diff --git a/rnd/market/Dockerfile b/rnd/market/Dockerfile new file mode 100644 index 000000000..ffb1ace7b --- /dev/null +++ b/rnd/market/Dockerfile @@ -0,0 +1,42 @@ +FROM python:3.11-slim-buster as server_base + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y build-essential curl ffmpeg wget libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && wget https://github.com/git/git/archive/v2.28.0.tar.gz -O git.tar.gz \ + && tar -zxf git.tar.gz \ + && cd git-* \ + && make prefix=/usr all \ + && make prefix=/usr install + + +ENV POETRY_VERSION=1.8.3 \ + POETRY_HOME="/opt/poetry" \ + POETRY_NO_INTERACTION=1 \ + POETRY_VIRTUALENVS_CREATE=false \ + PATH="$POETRY_HOME/bin:$PATH" +RUN pip3 install poetry + +COPY rnd/market /app/rnd/market + +WORKDIR /app/rnd/market + +# Install dependencies +RUN poetry install --no-interaction --no-ansi + +RUN poetry run prisma generate + +FROM server_base as server + +ENV PORT=8000 +ENV DATABASE_URL="" + +CMD ["poetry", "run", "app"] diff --git a/rnd/market/README.md b/rnd/market/README.md index 8b9bff449..a799f362b 100644 --- a/rnd/market/README.md +++ b/rnd/market/README.md @@ -1,14 +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 +## Key Features - Agent Discovery and Search - Agent Listings with Detailed Information - User Profiles -- Data Protection and Compliance \ No newline at end of file +- 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/rnd/market/linter.py b/rnd/market/linter.py deleted file mode 100644 index 83c574b03..000000000 --- a/rnd/market/linter.py +++ /dev/null @@ -1,27 +0,0 @@ -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 format(): - run("ruff", "check", "--fix", ".") - run("isort", "--profile", "black", ".") - run("black", ".") - run("pyright", ".") diff --git a/rnd/market/market/app.py b/rnd/market/market/app.py index 48209f505..2f7703500 100644 --- a/rnd/market/market/app.py +++ b/rnd/market/market/app.py @@ -1,60 +1,80 @@ - -from contextlib import asynccontextmanager +import contextlib import os -from dotenv import load_dotenv -from fastapi import FastAPI -from fastapi.middleware.gzip import GZipMiddleware -from prisma import Prisma + +import dotenv +import fastapi +import fastapi.middleware.cors +import fastapi.middleware.gzip +import prisma +import prometheus_fastapi_instrumentator import sentry_sdk -from sentry_sdk.integrations.asyncio import AsyncioIntegration -from sentry_sdk.integrations.fastapi import FastApiIntegration -from sentry_sdk.integrations.starlette import StarletteIntegration +import sentry_sdk.integrations.asyncio +import sentry_sdk.integrations.fastapi +import sentry_sdk.integrations.starlette -from market.routes import agents +import market.routes.admin +import market.routes.agents +import market.routes.search -load_dotenv() +dotenv.load_dotenv() if os.environ.get("SENTRY_DSN"): sentry_sdk.init( - dsn=os.environ.get("SENTRY_DSN"), - # Set traces_sample_rate to 1.0 to capture 100% - # of transactions for performance monitoring. + dsn=os.environ.get("SENTRY_DSN"), traces_sample_rate=1.0, - # Set profiles_sample_rate to 1.0 to profile 100% - # of sampled transactions. - # We recommend adjusting this value in production. profiles_sample_rate=1.0, enable_tracing=True, environment=os.environ.get("RUN_ENV", default="CLOUD").lower(), integrations=[ - StarletteIntegration(transaction_style="url"), - FastApiIntegration(transaction_style="url"), - AsyncioIntegration(), + sentry_sdk.integrations.starlette.StarletteIntegration( + transaction_style="url" + ), + sentry_sdk.integrations.fastapi.FastApiIntegration(transaction_style="url"), + sentry_sdk.integrations.asyncio.AsyncioIntegration(), ], ) -db_client = Prisma(auto_register=True) +db_client = prisma.Prisma(auto_register=True) -@asynccontextmanager -async def lifespan(app: FastAPI): +@contextlib.asynccontextmanager +async def lifespan(app: fastapi.FastAPI): await db_client.connect() yield await db_client.disconnect() -app = FastAPI( - title="Marketplace API", - description=( - "AutoGPT Marketplace API is a service that allows users to share AI agents." - ), +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", ) -# Add gzip middleware to compress responses -app.add_middleware(GZipMiddleware, minimum_size=1000) +app.add_middleware(fastapi.middleware.gzip.GZipMiddleware, minimum_size=1000) +app.add_middleware( + middleware_class=fastapi.middleware.cors.CORSMiddleware, + allow_origins=[ + # Currently, we allow only next.js dev server + "http://localhost:3000", + "http://127.0.0.1:3000", + ], + 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.admin.router, prefix="/admin", tags=["admin"]) -app.include_router(agents.router, prefix="/market/agents", tags=["agents"]) +@app.get("/health") +def health(): + return fastapi.responses.HTMLResponse( + content="

Marketplace API

", status_code=200 + ) + + +prometheus_fastapi_instrumentator.Instrumentator().instrument(app).expose(app) diff --git a/rnd/market/market/db.py b/rnd/market/market/db.py new file mode 100644 index 000000000..acf0a3251 --- /dev/null +++ b/rnd/market/market/db.py @@ -0,0 +1,440 @@ +import typing + +import fuzzywuzzy.fuzz +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 create_agent_entry( + name: str, + description: str, + author: str, + keywords: typing.List[str], + categories: typing.List[str], + graph: prisma.Json, +): + """ + 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}}, + } + ) + + 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 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, + 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} + + # 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", +) -> typing.List[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 = "" + 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' + + 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, + ts_rank(CAST(search AS tsvector), query.q) AS rank + FROM "Agents", query + WHERE 1=1 {category_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, + ) + + return results + + 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 +) -> TopAgentsDBResponse: + """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"}, + 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(analytics) + + return TopAgentsDBResponse( + analytics=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_featured: bool = True, category: str = "featured" +): + """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.") + + await prisma.models.FeaturedAgent.prisma().upsert( + where={"agentId": agent_id}, + data={ + "update": {"category": category, "is_featured": is_featured}, + "create": { + "category": category, + "is_featured": is_featured, + "agent": {"connect": {"id": agent_id}}, + }, + }, + ) + + 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 +) -> 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={"category": category, "is_featured": True}, + 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 diff --git a/rnd/market/market/model.py b/rnd/market/market/model.py new file mode 100644 index 000000000..71fea88fa --- /dev/null +++ b/rnd/market/market/model.py @@ -0,0 +1,88 @@ +import datetime +import typing + +import pydantic + + +class AddAgentRequest(pydantic.BaseModel): + graph: dict[str, typing.Any] + author: str + keywords: list[str] + categories: list[str] + + +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 + views: int = 0 + downloads: int = 0 + + +class AgentListResponse(pydantic.BaseModel): + """ + Represents a response containing a list of agents. + + Attributes: + agents (list[AgentResponse]): The list of 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. + """ + + agents: list[AgentResponse] + total_count: int + page: int + page_size: int + total_pages: int + + +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] diff --git a/rnd/market/market/routes/admin.py b/rnd/market/market/routes/admin.py new file mode 100644 index 000000000..40893a381 --- /dev/null +++ b/rnd/market/market/routes/admin.py @@ -0,0 +1,63 @@ +import fastapi +import prisma + +import market.db +import market.model + +router = fastapi.APIRouter() + + +@router.post("/agent", response_model=market.model.AgentResponse) +async def create_agent_entry(request: market.model.AddAgentRequest): + """ + A basic endpoint to create a new agent entry in the database. + + TODO: Protect endpoint! + """ + 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, category: str = "featured"): + """ + A basic endpoint to set an agent as featured in the database. + """ + try: + await market.db.set_agent_featured( + agent_id, is_featured=True, category=category + ) + return fastapi.responses.Response(status_code=200) + 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"): + """ + A basic endpoint to unset an agent as featured in the database. + """ + try: + await market.db.set_agent_featured( + agent_id, is_featured=False, category=category + ) + return fastapi.responses.Response(status_code=200) + 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/rnd/market/market/routes/agents.py b/rnd/market/market/routes/agents.py index af9233c56..e363a0647 100644 --- a/rnd/market/market/routes/agents.py +++ b/rnd/market/market/routes/agents.py @@ -1,3 +1,339 @@ -from fastapi import APIRouter +import json +import tempfile +import typing -router = APIRouter() +import fastapi +import fastapi.responses +import prisma + +import market.db +import market.model +import market.utils.analytics + +router = fastapi.APIRouter() + + +@router.get("/agents", response_model=market.model.AgentListResponse) +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)" + ), +): + """ + 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"). + + Returns: + market.model.AgentListResponse: 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, + ) + + agents = [ + market.model.AgentResponse(**agent.dict()) for agent in result["agents"] + ] + + return market.model.AgentListResponse( + agents=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.AgentListResponse) +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" + ), +): + """ + 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). + + Returns: + market.model.AgentListResponse: 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, + ) + + ret = market.model.AgentListResponse( + total_count=result.total_count, + page=result.page, + page_size=result.page_size, + total_pages=result.total_pages, + agents=[ + 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, + ) + for item in result.analytics + 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.AgentListResponse) +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" + ), +): + """ + 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). + + Returns: + market.model.AgentListResponse: 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, + ) + + ret = market.model.AgentListResponse( + total_count=result.total_count, + page=result.page, + page_size=result.page_size, + total_pages=result.total_pages, + agents=[ + 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 + ), + ) + 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/rnd/market/market/routes/search.py b/rnd/market/market/routes/search.py new file mode 100644 index 000000000..5964e2c8d --- /dev/null +++ b/rnd/market/market/routes/search.py @@ -0,0 +1,48 @@ +import typing + +import fastapi + +import market.db +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" + ), +) -> typing.List[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". + """ + return 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, + ) diff --git a/rnd/market/market/utils/analytics.py b/rnd/market/market/utils/analytics.py new file mode 100644 index 000000000..71dcaf078 --- /dev/null +++ b/rnd/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/rnd/market/market/utils/extension_types.py b/rnd/market/market/utils/extension_types.py new file mode 100644 index 000000000..d76bbb19f --- /dev/null +++ b/rnd/market/market/utils/extension_types.py @@ -0,0 +1,5 @@ +import prisma.models + + +class AgentsWithRank(prisma.models.Agents): + rank: float diff --git a/rnd/market/market/utils/partial_types.py b/rnd/market/market/utils/partial_types.py new file mode 100644 index 000000000..3ec3bdc2b --- /dev/null +++ b/rnd/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/rnd/market/migrations/20240731181721_base_migration/migration.sql b/rnd/market/migrations/20240731181721_base_migration/migration.sql new file mode 100644 index 000000000..57bdb6d04 --- /dev/null +++ b/rnd/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/rnd/market/migrations/20240731213728_unique_agent_id/migration.sql b/rnd/market/migrations/20240731213728_unique_agent_id/migration.sql new file mode 100644 index 000000000..fd421f6a6 --- /dev/null +++ b/rnd/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/rnd/market/migrations/20240802100955_add_featured_agents/migration.sql b/rnd/market/migrations/20240802100955_add_featured_agents/migration.sql new file mode 100644 index 000000000..e615e8671 --- /dev/null +++ b/rnd/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/rnd/market/migrations/20240802174953_default_is_featured_to_false/migration.sql b/rnd/market/migrations/20240802174953_default_is_featured_to_false/migration.sql new file mode 100644 index 000000000..0893c3785 --- /dev/null +++ b/rnd/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/rnd/market/migrations/migration_lock.toml b/rnd/market/migrations/migration_lock.toml new file mode 100644 index 000000000..fbffa92c2 --- /dev/null +++ b/rnd/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/rnd/market/poetry.lock b/rnd/market/poetry.lock index 20089dfe0..29abc6bc7 100644 --- a/rnd/market/poetry.lock +++ b/rnd/market/poetry.lock @@ -214,41 +214,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "email-validator" -version = "2.2.0" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, - {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -265,45 +230,36 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.111.1" +version = "0.109.2" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.111.1-py3-none-any.whl", hash = "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf"}, - {file = "fastapi-0.111.1.tar.gz", hash = "sha256:ddd1ac34cb1f76c2e2d7f8545a4bcb5463bce4834e81abf0b189e0c359ab2413"}, + {file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"}, + {file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"}, ] [package.dependencies] -email_validator = ">=2.0.0" -fastapi-cli = ">=0.0.2" -httpx = ">=0.23.0" -jinja2 = ">=2.11.2" 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" -python-multipart = ">=0.0.7" -starlette = ">=0.37.2,<0.38.0" +starlette = ">=0.36.3,<0.37.0" typing-extensions = ">=4.8.0" -uvicorn = {version = ">=0.12.0", extras = ["standard"]} [package.extras] -all = ["email_validator (>=2.0.0)", "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)"] +all = ["email-validator (>=2.0.0)", "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)"] [[package]] -name = "fastapi-cli" -version = "0.0.4" -description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" +name = "fuzzywuzzy" +version = "0.18.0" +description = "Fuzzy string matching in python" optional = false -python-versions = ">=3.8" +python-versions = "*" files = [ - {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"}, - {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"}, + {file = "fuzzywuzzy-0.18.0-py2.py3-none-any.whl", hash = "sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993"}, + {file = "fuzzywuzzy-0.18.0.tar.gz", hash = "sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8"}, ] -[package.dependencies] -typer = ">=0.12.3" - [package.extras] -standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"] +speedup = ["python-levenshtein (>=0.12)"] [[package]] name = "h11" @@ -337,54 +293,6 @@ http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] trio = ["trio (>=0.22.0,<0.26.0)"] -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - [[package]] name = "httpx" version = "0.27.0" @@ -463,28 +371,106 @@ MarkupSafe = ">=2.0" i18n = ["Babel (>=2.7)"] [[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" +name = "levenshtein" +version = "0.25.1" +description = "Python extension for computing string edit distances and similarities." optional = false python-versions = ">=3.8" files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, + {file = "Levenshtein-0.25.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb4d1ec9f2dcbde1757c4b7fb65b8682bc2de45b9552e201988f287548b7abdf"}, + {file = "Levenshtein-0.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4d9fa3affef48a7e727cdbd0d9502cd060da86f34d8b3627edd769d347570e2"}, + {file = "Levenshtein-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1b6cd186e58196ff8b402565317e9346b408d0c04fa0ed12ce4868c0fcb6d03"}, + {file = "Levenshtein-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82637ef5428384dd1812849dd7328992819bf0c4a20bff0a3b3ee806821af7ed"}, + {file = "Levenshtein-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e73656da6cc3e32a6e4bcd48562fcb64599ef124997f2c91f5320d7f1532c069"}, + {file = "Levenshtein-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5abff796f92cdfba69b9cbf6527afae918d0e95cbfac000bd84017f74e0bd427"}, + {file = "Levenshtein-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38827d82f2ca9cb755da6f03e686866f2f411280db005f4304272378412b4cba"}, + {file = "Levenshtein-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b989df1e3231261a87d68dfa001a2070771e178b09650f9cf99a20e3d3abc28"}, + {file = "Levenshtein-0.25.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2011d3b3897d438a2f88ef7aed7747f28739cae8538ec7c18c33dd989930c7a0"}, + {file = "Levenshtein-0.25.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6c375b33ec7acc1c6855e8ee8c7c8ac6262576ffed484ff5c556695527f49686"}, + {file = "Levenshtein-0.25.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ce0cb9dd012ef1bf4d5b9d40603e7709b6581aec5acd32fcea9b371b294ca7aa"}, + {file = "Levenshtein-0.25.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9da9ecb81bae67d784defed7274f894011259b038ec31f2339c4958157970115"}, + {file = "Levenshtein-0.25.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3bd7be5dbe5f4a1b691f381e39512927b39d1e195bd0ad61f9bf217a25bf36c9"}, + {file = "Levenshtein-0.25.1-cp310-cp310-win32.whl", hash = "sha256:f6abb9ced98261de67eb495b95e1d2325fa42b0344ed5763f7c0f36ee2e2bdba"}, + {file = "Levenshtein-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:97581af3e0a6d359af85c6cf06e51f77f4d635f7109ff7f8ed7fd634d8d8c923"}, + {file = "Levenshtein-0.25.1-cp310-cp310-win_arm64.whl", hash = "sha256:9ba008f490788c6d8d5a10735fcf83559965be97e4ef0812db388a84b1cc736a"}, + {file = "Levenshtein-0.25.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f57d9cf06dac55c2d2f01f0d06e32acc074ab9a902921dc8fddccfb385053ad5"}, + {file = "Levenshtein-0.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:22b60c6d791f4ca67a3686b557ddb2a48de203dae5214f220f9dddaab17f44bb"}, + {file = "Levenshtein-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d0444ee62eccf1e6cedc7c5bc01a9face6ff70cc8afa3f3ca9340e4e16f601a4"}, + {file = "Levenshtein-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e8758be8221a274c83924bae8dd8f42041792565a3c3bdd3c10e3f9b4a5f94e"}, + {file = "Levenshtein-0.25.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:147221cfb1d03ed81d22fdd2a4c7fc2112062941b689e027a30d2b75bbced4a3"}, + {file = "Levenshtein-0.25.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a454d5bc4f4a289f5471418788517cc122fcc00d5a8aba78c54d7984840655a2"}, + {file = "Levenshtein-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c25f3778bbac78286bef2df0ca80f50517b42b951af0a5ddaec514412f79fac"}, + {file = "Levenshtein-0.25.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:181486cf465aff934694cc9a19f3898a1d28025a9a5f80fc1608217e7cd1c799"}, + {file = "Levenshtein-0.25.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8db9f672a5d150706648b37b044dba61f36ab7216c6a121cebbb2899d7dfaa3"}, + {file = "Levenshtein-0.25.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f2a69fe5ddea586d439f9a50d0c51952982f6c0db0e3573b167aa17e6d1dfc48"}, + {file = "Levenshtein-0.25.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:3b684675a3bd35efa6997856e73f36c8a41ef62519e0267dcbeefd15e26cae71"}, + {file = "Levenshtein-0.25.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:cc707ef7edb71f6bf8339198b929ead87c022c78040e41668a4db68360129cef"}, + {file = "Levenshtein-0.25.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:41512c436b8c691326e2d07786d906cba0e92b5e3f455bf338befb302a0ca76d"}, + {file = "Levenshtein-0.25.1-cp311-cp311-win32.whl", hash = "sha256:2a3830175c01ade832ba0736091283f14a6506a06ffe8c846f66d9fbca91562f"}, + {file = "Levenshtein-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:9e0af4e6e023e0c8f79af1d1ca5f289094eb91201f08ad90f426d71e4ae84052"}, + {file = "Levenshtein-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:38e5d9a1d737d7b49fa17d6a4c03a0359288154bf46dc93b29403a9dd0cd1a7d"}, + {file = "Levenshtein-0.25.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4a40fa16ecd0bf9e557db67131aabeea957f82fe3e8df342aa413994c710c34e"}, + {file = "Levenshtein-0.25.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4f7d2045d5927cffa65a0ac671c263edbfb17d880fdce2d358cd0bda9bcf2b6d"}, + {file = "Levenshtein-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f96590539f9815be70e330b4d2efcce0219db31db5a22fffe99565192f5662"}, + {file = "Levenshtein-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d78512dd25b572046ff86d8903bec283c373063349f8243430866b6a9946425"}, + {file = "Levenshtein-0.25.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c161f24a1b216e8555c874c7dd70c1a0d98f783f252a16c9face920a8b8a6f3e"}, + {file = "Levenshtein-0.25.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06ebbfd010a00490795f478d18d7fa2ffc79c9c03fc03b678081f31764d16bab"}, + {file = "Levenshtein-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa9ec0a4489ebfb25a9ec2cba064ed68d0d2485b8bc8b7203f84a7874755e0f"}, + {file = "Levenshtein-0.25.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26408938a6db7b252824a701545d50dc9cdd7a3e4c7ee70834cca17953b76ad8"}, + {file = "Levenshtein-0.25.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:330ec2faff957281f4e6a1a8c88286d1453e1d73ee273ea0f937e0c9281c2156"}, + {file = "Levenshtein-0.25.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9115d1b08626dfdea6f3955cb49ba5a578f7223205f80ead0038d6fc0442ce13"}, + {file = "Levenshtein-0.25.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:bbd602edab758e93a5c67bf0d8322f374a47765f1cdb6babaf593a64dc9633ad"}, + {file = "Levenshtein-0.25.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b930b4df32cd3aabbed0e9f0c4fdd1ea4090a5c022ba9f1ae4ab70ccf1cf897a"}, + {file = "Levenshtein-0.25.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dd66fb51f88a3f73a802e1ff19a14978ddc9fbcb7ce3a667ca34f95ef54e0e44"}, + {file = "Levenshtein-0.25.1-cp312-cp312-win32.whl", hash = "sha256:386de94bd1937a16ae3c8f8b7dd2eff1b733994ecf56ce4d05dfdd0e776d0261"}, + {file = "Levenshtein-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:9ee1902153d47886c9787598a4a5c324ce7fde44d44daa34fcf3652ac0de21bc"}, + {file = "Levenshtein-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:b56a7e7676093c3aee50402226f4079b15bd21b5b8f1820f9d6d63fe99dc4927"}, + {file = "Levenshtein-0.25.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6b5dfdf6a0e2f35fd155d4c26b03398499c24aba7bc5db40245789c46ad35c04"}, + {file = "Levenshtein-0.25.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:355ff797f704459ddd8b95354d699d0d0642348636c92d5e67b49be4b0e6112b"}, + {file = "Levenshtein-0.25.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:933b827a3b721210fff522f3dca9572f9f374a0e88fa3a6c7ee3164406ae7794"}, + {file = "Levenshtein-0.25.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be1da669a240f272d904ab452ad0a1603452e190f4e03e886e6b3a9904152b89"}, + {file = "Levenshtein-0.25.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:265cbd78962503a26f2bea096258a3b70b279bb1a74a525c671d3ee43a190f9c"}, + {file = "Levenshtein-0.25.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63cc4d53a35e673b12b721a58b197b4a65734688fb72aa1987ce63ed612dca96"}, + {file = "Levenshtein-0.25.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75fee0c471b8799c70dad9d0d5b70f1f820249257f9617601c71b6c1b37bee92"}, + {file = "Levenshtein-0.25.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:045d6b0db124fbd37379b2b91f6d0786c2d9220e7a848e2dd31b99509a321240"}, + {file = "Levenshtein-0.25.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:db7a2e9c51ac9cc2fd5679484f1eac6e0ab2085cb181240445f7fbf10df73230"}, + {file = "Levenshtein-0.25.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c379c588aa0d93d4607db7eb225fd683263d49669b1bbe49e28c978aa6a4305d"}, + {file = "Levenshtein-0.25.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:966dd00424df7f69b78da02a29b530fbb6c1728e9002a2925ed7edf26b231924"}, + {file = "Levenshtein-0.25.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:09daa6b068709cc1e68b670a706d928ed8f0b179a26161dd04b3911d9f757525"}, + {file = "Levenshtein-0.25.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d6bed0792635081accf70a7e11cfece986f744fddf46ce26808cd8bfc067e430"}, + {file = "Levenshtein-0.25.1-cp38-cp38-win32.whl", hash = "sha256:28e7b7faf5a745a690d1b1706ab82a76bbe9fa6b729d826f0cfdd24fd7c19740"}, + {file = "Levenshtein-0.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:8ca0cc9b9e07316b5904f158d5cfa340d55b4a3566ac98eaac9f087c6efb9a1a"}, + {file = "Levenshtein-0.25.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:45682cdb3ac4a5465c01b2dce483bdaa1d5dcd1a1359fab37d26165b027d3de2"}, + {file = "Levenshtein-0.25.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f8dc3e63c4cd746ec162a4cd744c6dde857e84aaf8c397daa46359c3d54e6219"}, + {file = "Levenshtein-0.25.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:01ad1eb09933a499a49923e74e05b1428ca4ef37fed32965fef23f1334a11563"}, + {file = "Levenshtein-0.25.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbb4e8c4b8b7bbe0e1aa64710b806b6c3f31d93cb14969ae2c0eff0f3a592db8"}, + {file = "Levenshtein-0.25.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48d1fe224b365975002e3e2ea947cbb91d2936a16297859b71c4abe8a39932c"}, + {file = "Levenshtein-0.25.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a164df16d876aab0a400f72aeac870ea97947ea44777c89330e9a16c7dc5cc0e"}, + {file = "Levenshtein-0.25.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d3bcedcf64be6ceca423f6cfe29184a36d7c4cbac199fdc9a0a5ec7196cf5"}, + {file = "Levenshtein-0.25.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdaf62d637bef6711d6f3457e2684faab53b2db2ed53c05bc0dc856464c74742"}, + {file = "Levenshtein-0.25.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:af9de3b5f8f5f3530cfd97daab9ab480d1b121ef34d8c0aa5bab0c645eae219e"}, + {file = "Levenshtein-0.25.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:78fba73c352383b356a30c4674e39f086ffef7122fa625e7550b98be2392d387"}, + {file = "Levenshtein-0.25.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:9e0df0dcea3943321398f72e330c089b5d5447318310db6f17f5421642f3ade6"}, + {file = "Levenshtein-0.25.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:387f768bb201b9bc45f0f49557e2fb9a3774d9d087457bab972162dcd4fd352b"}, + {file = "Levenshtein-0.25.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dcf931b64311039b43495715e9b795fbd97ab44ba3dd6bf24360b15e4e87649"}, + {file = "Levenshtein-0.25.1-cp39-cp39-win32.whl", hash = "sha256:2449f8668c0bd62a2b305a5e797348984c06ac20903b38b3bab74e55671ddd51"}, + {file = "Levenshtein-0.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:28803fd6ec7b58065621f5ec0d24e44e2a7dc4842b64dcab690cb0a7ea545210"}, + {file = "Levenshtein-0.25.1-cp39-cp39-win_arm64.whl", hash = "sha256:0b074d452dff8ee86b5bdb6031aa32bb2ed3c8469a56718af5e010b9bb5124dc"}, + {file = "Levenshtein-0.25.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e9e060ef3925a68aeb12276f0e524fb1264592803d562ec0306c7c3f5c68eae0"}, + {file = "Levenshtein-0.25.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f84b84049318d44722db307c448f9dcb8d27c73525a378e901189a94889ba61"}, + {file = "Levenshtein-0.25.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e23fdf330cb185a0c7913ca5bd73a189dfd1742eae3a82e31ed8688b191800"}, + {file = "Levenshtein-0.25.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06958e4a81ea0f0b2b7768a2ad05bcd50a9ad04c4d521dd37d5730ff12decdc"}, + {file = "Levenshtein-0.25.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2ea7c34ec22b2fce21299b0caa6dde6bdebafcc2970e265853c9cfea8d1186da"}, + {file = "Levenshtein-0.25.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fddc0ccbdd94f57aa32e2eb3ac8310d08df2e175943dc20b3e1fc7a115850af4"}, + {file = "Levenshtein-0.25.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d52249cb3448bfe661d3d7db3a6673e835c7f37b30b0aeac499a1601bae873d"}, + {file = "Levenshtein-0.25.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8dd4c201b15f8c1e612f9074335392c8208ac147acbce09aff04e3974bf9b16"}, + {file = "Levenshtein-0.25.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23a4d95ce9d44161c7aa87ab76ad6056bc1093c461c60c097054a46dc957991f"}, + {file = "Levenshtein-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:65eea8a9c33037b23069dca4b3bc310e3c28ca53f60ec0c958d15c0952ba39fa"}, + {file = "Levenshtein-0.25.1.tar.gz", hash = "sha256:2df14471c778c75ffbd59cb64bbecfd4b0ef320ef9f80e4804764be7d5678980"}, ] [package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +rapidfuzz = ">=3.8.0,<4.0.0" [[package]] name = "markupsafe" @@ -555,17 +541,6 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - [[package]] name = "mypy-extensions" version = "1.0.0" @@ -643,13 +618,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prisma" -version = "0.12.0" +version = "0.13.1" description = "Prisma Client Python is an auto-generated and fully type-safe database client" optional = false python-versions = ">=3.7.0" files = [ - {file = "prisma-0.12.0-py3-none-any.whl", hash = "sha256:4b90438bb8030f71a33bfb66fdf2ce1e342a28ee524e4c2702d9f73643571d6f"}, - {file = "prisma-0.12.0.tar.gz", hash = "sha256:ec22804caae37555bd9ffa5efdcc630b2bed77187d13da5269715b1ad51a35f9"}, + {file = "prisma-0.13.1-py3-none-any.whl", hash = "sha256:b79ad69bdf09b217431904c1250c36421233ea394a230f1665f5699fd842ea20"}, + {file = "prisma-0.13.1.tar.gz", hash = "sha256:f0f86a67c38e6f08b53cce9272dd9c736f69f4fcbb94dbdfa87bf44f983e925d"}, ] [package.dependencies] @@ -657,16 +632,45 @@ click = ">=7.1.2" httpx = ">=0.19.0" jinja2 = ">=2.11.2" nodeenv = "*" -pydantic = ">=1.8.0,<3" +pydantic = ">=1.10.0,<3" python-dotenv = ">=0.12.0" StrEnum = {version = "*", markers = "python_version < \"3.11\""} tomlkit = "*" -typing-extensions = ">=4.0.1" +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" @@ -790,29 +794,15 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - [[package]] name = "pyright" -version = "1.1.373" +version = "1.1.374" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.373-py3-none-any.whl", hash = "sha256:b805413227f2c209f27b14b55da27fe5e9fb84129c9f1eb27708a5d12f6f000e"}, - {file = "pyright-1.1.373.tar.gz", hash = "sha256:f41bcfc8b9d1802b09921a394d6ae1ce19694957b628bc657629688daf8a83ff"}, + {file = "pyright-1.1.374-py3-none-any.whl", hash = "sha256:55752bcf7a3646d293cd76710a983b71e16f6128aab2d42468e6eb7e46c0a70d"}, + {file = "pyright-1.1.374.tar.gz", hash = "sha256:d01b2daf864ba5e0362e56b844984865970d7204158e61eb685e2dab7804cb82"}, ] [package.dependencies] @@ -892,77 +882,123 @@ files = [ cli = ["click (>=5.0)"] [[package]] -name = "python-multipart" -version = "0.0.9" -description = "A streaming multipart parser for Python" +name = "python-levenshtein" +version = "0.25.1" +description = "Python extension for computing string edit distances and similarities." optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, + {file = "python-Levenshtein-0.25.1.tar.gz", hash = "sha256:b21e7efe83c8e8dc8260f2143b2393c6c77cb2956f0c53de6c4731c4d8006acc"}, + {file = "python_Levenshtein-0.25.1-py3-none-any.whl", hash = "sha256:654446d1ea4acbcc573d44c43775854834a7547e4cb2f79f638f738134d72037"}, +] + +[package.dependencies] +Levenshtein = "0.25.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] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] +full = ["numpy"] [[package]] name = "requests" @@ -985,24 +1021,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "rich" -version = "13.7.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - [[package]] name = "ruff" version = "0.5.5" @@ -1032,13 +1050,13 @@ files = [ [[package]] name = "sentry-sdk" -version = "2.11.0" +version = "2.12.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.11.0-py2.py3-none-any.whl", hash = "sha256:d964710e2dbe015d9dc4ff0ad16225d68c3b36936b742a6fe0504565b760a3b7"}, - {file = "sentry_sdk-2.11.0.tar.gz", hash = "sha256:4ca16e9f5c7c6bc2fb2d5c956219f4926b148e511fffdbbde711dc94f1e0468f"}, + {file = "sentry_sdk-2.12.0-py2.py3-none-any.whl", hash = "sha256:7a8d5163d2ba5c5f4464628c6b68f85e86972f7c636acc78aed45c61b98b7a5e"}, + {file = "sentry_sdk-2.12.0.tar.gz", hash = "sha256:8763840497b817d44c49b3fe3f5f7388d083f2337ffedf008b2cdb63b5c86dc6"}, ] [package.dependencies] @@ -1069,7 +1087,7 @@ langchain = ["langchain (>=0.0.210)"] loguru = ["loguru (>=0.5)"] openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"] +opentelemetry-experimental = ["opentelemetry-distro"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -1081,17 +1099,6 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=6)"] -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -1105,13 +1112,13 @@ files = [ [[package]] name = "starlette" -version = "0.37.2" +version = "0.36.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, + {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, + {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, ] [package.dependencies] @@ -1158,23 +1165,6 @@ files = [ {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, ] -[[package]] -name = "typer" -version = "0.12.3" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.7" -files = [ - {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, - {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, -] - -[package.dependencies] -click = ">=8.0.0" -rich = ">=10.11.0" -shellingham = ">=1.3.0" -typing-extensions = ">=3.7.4.3" - [[package]] name = "typing-extensions" version = "4.12.2" @@ -1205,74 +1195,23 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.30.3" +version = "0.30.4" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"}, - {file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"}, + {file = "uvicorn-0.30.4-py3-none-any.whl", hash = "sha256:06b00e3087e58c6865c284143c0c42f810b32ff4f265ab19d08c566f74a08728"}, + {file = "uvicorn-0.30.4.tar.gz", hash = "sha256:00db9a9e3711a5fa59866e2b02fac69d8dc70ce0814aaec9a66d1d9e5c832a30"}, ] [package.dependencies] click = ">=7.0" -colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} -python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} -watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} -websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "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 = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - [[package]] name = "watchdog" version = "4.0.1" @@ -1317,175 +1256,7 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] -[[package]] -name = "watchfiles" -version = "0.22.0" -description = "Simple, modern and high performance file watching and code reload in python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, - {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, - {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, - {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, - {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, - {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, - {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, - {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, - {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, - {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, - {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, - {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, - {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, - {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, - {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, - {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, - {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, - {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, - {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, - {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, - {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, - {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, - {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, - {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, - {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, - {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, - {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, - {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, - {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, - {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, - {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, - {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, -] - -[package.dependencies] -anyio = ">=3.0.0" - -[[package]] -name = "websockets" -version = "12.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, -] - [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "8f44ca82bd8d4a16c3f644cff2421d14f5aa6a36da0ba683b7ac9e883b4212b5" +content-hash = "f48dca64557d652682ac309935aa15d06d5a1f7b887b87af7f6bcca57d0a54ca" diff --git a/rnd/market/pyproject.toml b/rnd/market/pyproject.toml index ad00cfece..d2339f7b0 100644 --- a/rnd/market/pyproject.toml +++ b/rnd/market/pyproject.toml @@ -2,16 +2,24 @@ name = "market" version = "0.1.0" description = "" -authors = ["SwiftyOS "] +authors = [ + "SwiftyOS ", + "Nicholas Tindle ", +] readme = "README.md" [tool.poetry.dependencies] python = "^3.10" -prisma = "^0.12.0" +prisma = "^0.13.1" python-dotenv = "^1.0.1" uvicorn = "^0.30.3" -fastapi = "^0.111.1" -sentry-sdk = {extras = ["fastapi"], version = "^2.11.0"} +fastapi = "^0.109.0" +sentry-sdk = { extras = ["fastapi"], version = "^2.11.0" } +fuzzywuzzy = "^0.18.0" +python-levenshtein = "^0.25.1" +# autogpt-server = { path = "../autogpt_server", develop = true } +prometheus-fastapi-instrumentator = "^7.0.0" + [tool.poetry.group.dev.dependencies] pytest = "^8.2.1" @@ -29,8 +37,11 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -format = "linter:format" -lint = "linter:lint" +format = "scripts:format" +lint = "scripts:lint" +app = "scripts:app" +setup = "scripts:setup" +populate = "scripts:populate_database" [tool.pytest-watcher] now = false diff --git a/rnd/market/schema.prisma b/rnd/market/schema.prisma index e1c71cc4b..9e4d020d3 100644 --- a/rnd/market/schema.prisma +++ b/rnd/market/schema.prisma @@ -4,75 +4,50 @@ datasource db { } generator client { - provider = "prisma-client-py" - recursive_type_depth = 5 - interface = "asyncio" + provider = "prisma-client-py" + recursive_type_depth = 5 + interface = "asyncio" + previewFeatures = ["fullTextSearch"] + partial_type_generator = "market/utils/partial_types.py" } -// This model describes the Agent Graph/Flow (Multi Agent System). -model AgentGraph { - id String @default(uuid()) - version Int @default(1) +model Agents { + id String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + version Int @default(1) name String? description String? - isActive Boolean @default(true) - isTemplate Boolean @default(false) + author String? - AgentNodes AgentNode[] + keywords String[] + categories String[] + search Unsupported("tsvector")? @default(dbgenerated("''::tsvector")) + + graph Json + AnalyticsTracker AnalyticsTracker[] + FeaturedAgent FeaturedAgent? @@id(name: "graphVersionId", [id, version]) } -// This model describes a single node in the Agent Graph/Flow (Multi Agent System). -model AgentNode { - id String @id @default(uuid()) - - agentBlockId String - AgentBlock AgentBlock @relation(fields: [agentBlockId], references: [id]) - - agentGraphId String - agentGraphVersion Int @default(1) - AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version]) - - // List of consumed input, that the parent node should provide. - Input AgentNodeLink[] @relation("AgentNodeSink") - - // List of produced output, that the child node should be executed. - Output AgentNodeLink[] @relation("AgentNodeSource") - - // JSON serialized dict[str, str] containing predefined input values. - constantInput String @default("{}") - - // JSON serialized dict[str, str] containing the node metadata. - metadata String @default("{}") +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]) + views Int + downloads Int } -// This model describes the link between two AgentNodes. -model AgentNodeLink { - id String @id @default(uuid()) +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]) + is_featured Boolean @default(false) + category String @default("featured") - // Output of a node is connected to the source of the link. - agentNodeSourceId String - AgentNodeSource AgentNode @relation("AgentNodeSource", fields: [agentNodeSourceId], references: [id]) - sourceName String - - // Input of a node is connected to the sink of the link. - agentNodeSinkId String - AgentNodeSink AgentNode @relation("AgentNodeSink", fields: [agentNodeSinkId], references: [id]) - sinkName String -} - -// This model describes a component that will be executed by the AgentNode. -model AgentBlock { - id String @id @default(uuid()) - name String @unique - - // We allow a block to have multiple types of input & output. - // Serialized object-typed `jsonschema` with top-level properties as input/output name. - inputSchema String - outputSchema String - - // Prisma requires explicit back-references. - ReferencedByAgentNode AgentNode[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } diff --git a/rnd/market/scripts.py b/rnd/market/scripts.py new file mode 100644 index 000000000..49b44d255 --- /dev/null +++ b/rnd/market/scripts.py @@ -0,0 +1,66 @@ +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 / "autogpt_server" / "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:8001/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(): + run("uvicorn", "market.app:app", "--reload", "--port", "8001") + + +def setup(): + run("prisma", "generate") + run("prisma", "migrate", "deploy") diff --git a/rnd/market/tests/test_agents.py b/rnd/market/tests/test_agents.py new file mode 100644 index 000000000..bcf9f033f --- /dev/null +++ b/rnd/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