Merge branch 'dev' into kpczerwinski/open-2263-change-url-from-store-to-marketplace

This commit is contained in:
Krzysztof Czerwinski 2025-01-07 13:16:57 +01:00
commit f4f5c9af80
64 changed files with 1557 additions and 4133 deletions

View File

@ -1,40 +1,61 @@
# Ignore everything by default, selectively add things to context
classic/run
*
# AutoGPT
# Platform - Libs
!autogpt_platform/autogpt_libs/autogpt_libs/
!autogpt_platform/autogpt_libs/pyproject.toml
!autogpt_platform/autogpt_libs/poetry.lock
!autogpt_platform/autogpt_libs/README.md
# Platform - Backend
!autogpt_platform/backend/backend/
!autogpt_platform/backend/migrations/
!autogpt_platform/backend/schema.prisma
!autogpt_platform/backend/pyproject.toml
!autogpt_platform/backend/poetry.lock
!autogpt_platform/backend/README.md
# Platform - Market
!autogpt_platform/market/market/
!autogpt_platform/market/scripts.py
!autogpt_platform/market/schema.prisma
!autogpt_platform/market/pyproject.toml
!autogpt_platform/market/poetry.lock
!autogpt_platform/market/README.md
# Platform - Frontend
!autogpt_platform/frontend/src/
!autogpt_platform/frontend/public/
!autogpt_platform/frontend/package.json
!autogpt_platform/frontend/yarn.lock
!autogpt_platform/frontend/tsconfig.json
!autogpt_platform/frontend/README.md
## config
!autogpt_platform/frontend/*.config.*
!autogpt_platform/frontend/.env.*
# Classic - AutoGPT
!classic/original_autogpt/autogpt/
!classic/original_autogpt/pyproject.toml
!classic/original_autogpt/poetry.lock
!classic/original_autogpt/README.md
!classic/original_autogpt/tests/
# Benchmark
# Classic - Benchmark
!classic/benchmark/agbenchmark/
!classic/benchmark/pyproject.toml
!classic/benchmark/poetry.lock
!classic/benchmark/README.md
# Forge
# Classic - Forge
!classic/forge/
!classic/forge/pyproject.toml
!classic/forge/poetry.lock
!classic/forge/README.md
# Frontend
# Classic - Frontend
!classic/frontend/build/web/
# Platform
!autogpt_platform/
# Explicitly re-ignore some folders
.*
**/__pycache__
autogpt_platform/frontend/.next/
autogpt_platform/frontend/node_modules
autogpt_platform/frontend/.env.example
autogpt_platform/frontend/.env.local
autogpt_platform/backend/.env
autogpt_platform/backend/.venv/
autogpt_platform/market/.env

View File

@ -89,28 +89,6 @@ updates:
- "minor"
- "patch"
# market (Poetry project)
- package-ecosystem: "pip"
directory: "autogpt_platform/market"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: "dev"
commit-message:
prefix: "chore(market/deps)"
prefix-development: "chore(market/deps-dev)"
groups:
production-dependencies:
dependency-type: "production"
update-types:
- "minor"
- "patch"
development-dependencies:
dependency-type: "development"
update-types:
- "minor"
- "patch"
# GitHub Actions
- package-ecosystem: "github-actions"

View File

@ -35,12 +35,6 @@ jobs:
env:
DATABASE_URL: ${{ secrets.BACKEND_DATABASE_URL }}
- name: Run Market Migrations
working-directory: ./autogpt_platform/market
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.MARKET_DATABASE_URL }}
trigger:
needs: migrate

View File

@ -37,13 +37,6 @@ jobs:
env:
DATABASE_URL: ${{ secrets.BACKEND_DATABASE_URL }}
- name: Run Market Migrations
working-directory: ./autogpt_platform/market
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.MARKET_DATABASE_URL }}
trigger:
needs: migrate
runs-on: ubuntu-latest

View File

@ -81,7 +81,7 @@ jobs:
- name: Check poetry.lock
run: |
poetry lock --no-update
poetry lock
if ! git diff --quiet poetry.lock; then
echo "Error: poetry.lock not up to date."

View File

@ -88,6 +88,11 @@ jobs:
run: |
yarn test --project=${{ matrix.browser }}
- name: Print Docker Compose logs in debug mode
if: runner.debug
run: |
docker compose -f ../docker-compose.yml logs
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:

View File

@ -1,126 +0,0 @@
name: AutoGPT Platform - Backend CI
on:
push:
branches: [master, dev, ci-test*]
paths:
- ".github/workflows/platform-market-ci.yml"
- "autogpt_platform/market/**"
pull_request:
branches: [master, dev, release-*]
paths:
- ".github/workflows/platform-market-ci.yml"
- "autogpt_platform/market/**"
merge_group:
concurrency:
group: ${{ format('backend-ci-{0}', github.head_ref && format('{0}-{1}', github.event_name, github.event.pull_request.number) || github.sha) }}
cancel-in-progress: ${{ startsWith(github.event_name, 'pull_request') }}
defaults:
run:
shell: bash
working-directory: autogpt_platform/market
jobs:
test:
permissions:
contents: read
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.10"]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup Supabase
uses: supabase/setup-cli@v1
with:
version: latest
- id: get_date
name: Get date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Set up Python dependency cache
uses: actions/cache@v4
with:
path: ~/.cache/pypoetry
key: poetry-${{ runner.os }}-${{ hashFiles('autogpt_platform/market/poetry.lock') }}
- name: Install Poetry (Unix)
run: |
curl -sSL https://install.python-poetry.org | python3 -
if [ "${{ runner.os }}" = "macOS" ]; then
PATH="$HOME/.local/bin:$PATH"
echo "$HOME/.local/bin" >> $GITHUB_PATH
fi
- name: Install Python dependencies
run: poetry install
- name: Generate Prisma Client
run: poetry run prisma generate
- id: supabase
name: Start Supabase
working-directory: .
run: |
supabase init
supabase start --exclude postgres-meta,realtime,storage-api,imgproxy,inbucket,studio,edge-runtime,logflare,vector,supavisor
supabase status -o env | sed 's/="/=/; s/"$//' >> $GITHUB_OUTPUT
# outputs:
# DB_URL, API_URL, GRAPHQL_URL, ANON_KEY, SERVICE_ROLE_KEY, JWT_SECRET
- name: Run Database Migrations
run: poetry run prisma migrate dev --name updates
env:
DATABASE_URL: ${{ steps.supabase.outputs.DB_URL }}
- id: lint
name: Run Linter
run: poetry run lint
# Tests comment out because they do not work with prisma mock, nor have they been updated since they were created
# - name: Run pytest with coverage
# run: |
# if [[ "${{ runner.debug }}" == "1" ]]; then
# poetry run pytest -s -vv -o log_cli=true -o log_cli_level=DEBUG test
# else
# poetry run pytest -s -vv test
# fi
# if: success() || (failure() && steps.lint.outcome == 'failure')
# env:
# LOG_LEVEL: ${{ runner.debug && 'DEBUG' || 'INFO' }}
# DATABASE_URL: ${{ steps.supabase.outputs.DB_URL }}
# SUPABASE_URL: ${{ steps.supabase.outputs.API_URL }}
# SUPABASE_SERVICE_ROLE_KEY: ${{ steps.supabase.outputs.SERVICE_ROLE_KEY }}
# SUPABASE_JWT_SECRET: ${{ steps.supabase.outputs.JWT_SECRET }}
# REDIS_HOST: 'localhost'
# REDIS_PORT: '6379'
# REDIS_PASSWORD: 'testpassword'
env:
CI: true
PLAIN_OUTPUT: True
RUN_ENV: local
PORT: 8080
# - name: Upload coverage reports to Codecov
# uses: codecov/codecov-action@v4
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# flags: backend,${{ runner.os }}

View File

@ -110,7 +110,7 @@ repos:
- id: isort
name: Lint (isort) - AutoGPT Platform - Backend
alias: isort-platform-backend
entry: poetry -C autogpt_platform/backend run isort -p backend
entry: poetry -P autogpt_platform/backend run isort -p backend
files: ^autogpt_platform/backend/
types: [file, python]
language: system
@ -118,7 +118,7 @@ repos:
- id: isort
name: Lint (isort) - Classic - AutoGPT
alias: isort-classic-autogpt
entry: poetry -C classic/original_autogpt run isort -p autogpt
entry: poetry -P classic/original_autogpt run isort -p autogpt
files: ^classic/original_autogpt/
types: [file, python]
language: system
@ -126,7 +126,7 @@ repos:
- id: isort
name: Lint (isort) - Classic - Forge
alias: isort-classic-forge
entry: poetry -C classic/forge run isort -p forge
entry: poetry -P classic/forge run isort -p forge
files: ^classic/forge/
types: [file, python]
language: system
@ -134,7 +134,7 @@ repos:
- id: isort
name: Lint (isort) - Classic - Benchmark
alias: isort-classic-benchmark
entry: poetry -C classic/benchmark run isort -p agbenchmark
entry: poetry -P classic/benchmark run isort -p agbenchmark
files: ^classic/benchmark/
types: [file, python]
language: system
@ -178,7 +178,6 @@ repos:
name: Typecheck - AutoGPT Platform - Backend
alias: pyright-platform-backend
entry: poetry -C autogpt_platform/backend run pyright
args: [-p, autogpt_platform/backend, autogpt_platform/backend]
# include forge source (since it's a path dependency) but exclude *_test.py files:
files: ^autogpt_platform/(backend/((backend|test)/|(\w+\.py|poetry\.lock)$)|autogpt_libs/(autogpt_libs/.*(?<!_test)\.py|poetry\.lock)$)
types: [file]
@ -189,7 +188,6 @@ repos:
name: Typecheck - AutoGPT Platform - Libs
alias: pyright-platform-libs
entry: poetry -C autogpt_platform/autogpt_libs run pyright
args: [-p, autogpt_platform/autogpt_libs, autogpt_platform/autogpt_libs]
files: ^autogpt_platform/autogpt_libs/(autogpt_libs/|poetry\.lock$)
types: [file]
language: system
@ -199,7 +197,6 @@ repos:
name: Typecheck - Classic - AutoGPT
alias: pyright-classic-autogpt
entry: poetry -C classic/original_autogpt run pyright
args: [-p, classic/original_autogpt, classic/original_autogpt]
# include forge source (since it's a path dependency) but exclude *_test.py files:
files: ^(classic/original_autogpt/((autogpt|scripts|tests)/|poetry\.lock$)|classic/forge/(forge/.*(?<!_test)\.py|poetry\.lock)$)
types: [file]
@ -210,7 +207,6 @@ repos:
name: Typecheck - Classic - Forge
alias: pyright-classic-forge
entry: poetry -C classic/forge run pyright
args: [-p, classic/forge, classic/forge]
files: ^classic/forge/(forge/|poetry\.lock$)
types: [file]
language: system
@ -220,7 +216,6 @@ repos:
name: Typecheck - Classic - Benchmark
alias: pyright-classic-benchmark
entry: poetry -C classic/benchmark run pyright
args: [-p, classic/benchmark, classic/benchmark]
files: ^classic/benchmark/(agbenchmark/|tests/|poetry\.lock$)
types: [file]
language: system

View File

@ -1415,29 +1415,29 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.8.3"
version = "0.8.6"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
{file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
{file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
{file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
{file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
{file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
{file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
{file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"},
{file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"},
{file = "ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf"},
{file = "ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117"},
{file = "ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe"},
{file = "ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d"},
{file = "ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a"},
{file = "ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76"},
{file = "ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764"},
{file = "ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"},
{file = "ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162"},
{file = "ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5"},
]
[[package]]
@ -1852,4 +1852,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<4.0"
content-hash = "13a36d3be675cab4a3eb2e6a62a1b08df779bded4c7b9164d8be300dc08748d0"
content-hash = "bf1b0125759dadb1369fff05ffba64fea3e82b9b7a43d0068e1c80974a4ebc1c"

View File

@ -21,7 +21,7 @@ supabase = "^2.10.0"
[tool.poetry.group.dev.dependencies]
redis = "^5.2.1"
ruff = "^0.8.3"
ruff = "^0.8.6"
[build-system]
requires = ["poetry-core"]

View File

@ -17,12 +17,11 @@ RUN apt-get install -y libz-dev
RUN apt-get install -y libssl-dev
RUN apt-get install -y postgresql-client
ENV POETRY_VERSION=1.8.3
ENV POETRY_HOME=/opt/poetry
ENV POETRY_NO_INTERACTION=1
ENV POETRY_VIRTUALENVS_CREATE=false
ENV PATH=/opt/poetry/bin:$PATH
# Upgrade pip and setuptools to fix security vulnerabilities
RUN pip3 install --upgrade pip setuptools
@ -32,25 +31,21 @@ RUN pip3 install poetry
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
COPY autogpt_platform/backend/poetry.lock autogpt_platform/backend/pyproject.toml /app/autogpt_platform/backend/
WORKDIR /app/autogpt_platform/backend
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi
RUN poetry install --no-ansi --no-root
# Generate Prisma client
COPY autogpt_platform/backend/schema.prisma ./
RUN poetry config virtualenvs.create false \
&& poetry run prisma generate
RUN poetry run prisma generate
FROM python:3.11.10-slim-bookworm AS server_dependencies
WORKDIR /app
ENV POETRY_VERSION=1.8.3
ENV POETRY_HOME=/opt/poetry
ENV POETRY_NO_INTERACTION=1
ENV POETRY_VIRTUALENVS_CREATE=false
ENV POETRY_HOME=/opt/poetry \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false
ENV PATH=/opt/poetry/bin:$PATH
# Upgrade pip and setuptools to fix security vulnerabilities
RUN pip3 install --upgrade pip setuptools
@ -76,6 +71,7 @@ WORKDIR /app/autogpt_platform/backend
FROM server_dependencies AS server
COPY autogpt_platform/backend /app/autogpt_platform/backend
RUN poetry install --no-ansi --only-root
ENV DATABASE_URL=""
ENV PORT=8000

View File

@ -76,7 +76,11 @@ class AgentExecutorBlock(Block):
)
if not event.node_id:
if event.status in [ExecutionStatus.COMPLETED, ExecutionStatus.FAILED]:
if event.status in [
ExecutionStatus.COMPLETED,
ExecutionStatus.TERMINATED,
ExecutionStatus.FAILED,
]:
logger.info(f"Execution {log_id} ended with status {event.status}")
break
else:

View File

@ -270,9 +270,9 @@ async def update_graph_execution_start_time(graph_exec_id: str):
async def update_graph_execution_stats(
graph_exec_id: str,
status: ExecutionStatus,
stats: dict[str, Any],
) -> ExecutionResult:
status = ExecutionStatus.FAILED if stats.get("error") else ExecutionStatus.COMPLETED
res = await AgentGraphExecution.prisma().update(
where={"id": graph_exec_id},
data={

View File

@ -629,25 +629,20 @@ async def __create_graph(tx, graph: Graph, user_id: str):
"isTemplate": graph.is_template,
"isActive": graph.is_active,
"userId": user_id,
"AgentNodes": {
"create": [
{
"id": node.id,
"agentBlockId": node.block_id,
"constantInput": json.dumps(node.input_default),
"metadata": json.dumps(node.metadata),
}
for node in graph.nodes
]
},
}
)
await asyncio.gather(
*[
AgentNode.prisma(tx).create(
{
"id": node.id,
"agentBlockId": node.block_id,
"agentGraphId": graph.id,
"agentGraphVersion": graph.version,
"constantInput": json.dumps(node.input_default),
"metadata": json.dumps(node.metadata),
}
)
for node in graph.nodes
]
)
await asyncio.gather(
*[
AgentNodeLink.prisma(tx).create(

View File

@ -35,7 +35,7 @@ class Webhook(BaseDbModel):
@computed_field
@property
def url(self) -> str:
return webhook_ingress_url(self.provider.value, self.id)
return webhook_ingress_url(self.provider, self.id)
@staticmethod
def from_db(webhook: IntegrationWebhook):

View File

@ -597,7 +597,7 @@ class Executor:
node_eid="*",
block_name="-",
)
timing_info, (exec_stats, error) = cls._on_graph_execution(
timing_info, (exec_stats, status, error) = cls._on_graph_execution(
graph_exec, cancel, log_metadata
)
exec_stats["walltime"] = timing_info.wall_time
@ -605,6 +605,7 @@ class Executor:
exec_stats["error"] = str(error) if error else None
result = cls.db_client.update_graph_execution_stats(
graph_exec_id=graph_exec.graph_exec_id,
status=status,
stats=exec_stats,
)
cls.db_client.send_execution_update(result)
@ -616,11 +617,12 @@ class Executor:
graph_exec: GraphExecutionEntry,
cancel: threading.Event,
log_metadata: LogMetadata,
) -> tuple[dict[str, Any], Exception | None]:
) -> tuple[dict[str, Any], ExecutionStatus, Exception | None]:
"""
Returns:
The execution statistics of the graph execution.
The error that occurred during the execution.
dict: The execution statistics of the graph execution.
ExecutionStatus: The final status of the graph execution.
Exception | None: The error that occurred during the execution, if any.
"""
log_metadata.info(f"Start graph execution {graph_exec.graph_exec_id}")
exec_stats = {
@ -665,8 +667,7 @@ class Executor:
while not queue.empty():
if cancel.is_set():
error = RuntimeError("Execution is cancelled")
return exec_stats, error
return exec_stats, ExecutionStatus.TERMINATED, error
exec_data = queue.get()
@ -696,8 +697,7 @@ class Executor:
)
for node_id, execution in list(running_executions.items()):
if cancel.is_set():
error = RuntimeError("Execution is cancelled")
return exec_stats, error
return exec_stats, ExecutionStatus.TERMINATED, error
if not queue.empty():
break # yield to parent loop to execute new queue items
@ -716,7 +716,12 @@ class Executor:
finished = True
cancel.set()
cancel_thread.join()
return exec_stats, error
return (
exec_stats,
ExecutionStatus.FAILED if error else ExecutionStatus.COMPLETED,
error,
)
class ExecutionManager(AppService):
@ -882,11 +887,8 @@ class ExecutionManager(AppService):
ExecutionStatus.COMPLETED,
ExecutionStatus.FAILED,
):
self.db_client.upsert_execution_output(
node_exec.node_exec_id, "error", "TERMINATED"
)
exec_update = self.db_client.update_execution_status(
node_exec.node_exec_id, ExecutionStatus.FAILED
node_exec.node_exec_id, ExecutionStatus.TERMINATED
)
self.db_client.send_execution_update(exec_update)

View File

@ -1,10 +1,11 @@
from backend.integrations.providers import ProviderName
from backend.util.settings import Config
app_config = Config()
# TODO: add test to assert this matches the actual API route
def webhook_ingress_url(provider_name: str, webhook_id: str) -> str:
def webhook_ingress_url(provider_name: ProviderName, webhook_id: str) -> str:
return (
f"{app_config.platform_base_url}/api/integrations/{provider_name}"
f"/webhooks/{webhook_id}/ingress"

View File

@ -65,6 +65,9 @@ async def wait_execution(
if status == ExecutionStatus.FAILED:
log.info("Execution failed")
raise Exception("Execution failed")
if status == ExecutionStatus.TERMINATED:
log.info("Execution terminated")
raise Exception("Execution terminated")
return status == ExecutionStatus.COMPLETED
# Wait for the executions to complete

View File

@ -0,0 +1,2 @@
-- Add "TERMINATED" to execution status enum type
ALTER TYPE "AgentExecutionStatus" ADD VALUE 'TERMINATED';

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ version = "0.3.4"
description = "A platform for building AI-powered agentic workflows"
authors = ["AutoGPT <info@agpt.co>"]
readme = "README.md"
packages = [{ include = "backend" }]
packages = [{ include = "backend", format = "sdist" }]
[tool.poetry.dependencies]

View File

@ -216,6 +216,7 @@ enum AgentExecutionStatus {
QUEUED
RUNNING
COMPLETED
TERMINATED
FAILED
}
@ -638,4 +639,4 @@ enum APIKeyStatus {
ACTIVE
REVOKED
SUSPENDED
}
}

View File

@ -144,51 +144,6 @@ services:
networks:
- app-network
market:
build:
context: ../
dockerfile: autogpt_platform/market/Dockerfile
develop:
watch:
- path: ./
target: autogpt_platform/market/
action: rebuild
depends_on:
db:
condition: service_healthy
market-migrations:
condition: service_completed_successfully
environment:
- SUPABASE_URL=http://kong:8000
- SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
- SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
- DATABASE_URL=postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=market
- BACKEND_CORS_ALLOW_ORIGINS="http://localhost:3000,http://127.0.0.1:3000"
ports:
- "8015:8015"
networks:
- app-network
market-migrations:
build:
context: ../
dockerfile: autogpt_platform/market/Dockerfile
command: ["sh", "-c", "poetry run prisma migrate deploy"]
develop:
watch:
- path: ./
target: autogpt_platform/market/
action: rebuild
depends_on:
db:
condition: service_healthy
environment:
- SUPABASE_URL=http://kong:8000
- SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
- SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
- DATABASE_URL=postgresql://postgres:your-super-secret-and-long-postgres-password@db:5432/postgres?connect_timeout=60&schema=market
networks:
- app-network
# frontend:
# build:
# context: ../

View File

@ -51,18 +51,6 @@ services:
file: ./docker-compose.platform.yml
service: websocket_server
market:
<<: *agpt-services
extends:
file: ./docker-compose.platform.yml
service: market
market-migrations:
<<: *agpt-services
extends:
file: ./docker-compose.platform.yml
service: market-migrations
# frontend:
# <<: *agpt-services
# extends:

View File

@ -848,8 +848,10 @@ export function CustomNode({
data.status === "COMPLETED",
"border-yellow-600 bg-yellow-600 text-white":
data.status === "RUNNING",
"border-red-600 bg-red-600 text-white":
data.status === "FAILED",
"border-red-600 bg-red-600 text-white": [
"FAILED",
"TERMINATED",
].includes(data.status || ""),
"border-blue-600 bg-blue-600 text-white":
data.status === "QUEUED",
"border-gray-600 bg-gray-600 font-black":

View File

@ -558,8 +558,9 @@ export default function useAgentGraph(
return;
}
if (
nodeResult.status != "COMPLETED" &&
nodeResult.status != "FAILED"
!["COMPLETED", "TERMINATED", "FAILED"].includes(
nodeResult.status,
)
) {
pendingNodeExecutions.add(nodeResult.node_exec_id);
} else {

View File

@ -196,7 +196,7 @@ export type GraphExecution = {
ended_at: number;
duration: number;
total_run_time: number;
status: "INCOMPLETE" | "QUEUED" | "RUNNING" | "COMPLETED" | "FAILED";
status: "QUEUED" | "RUNNING" | "COMPLETED" | "TERMINATED" | "FAILED";
graph_id: string;
graph_version: number;
};
@ -246,7 +246,13 @@ export type NodeExecutionResult = {
node_exec_id: string;
node_id: string;
block_id: string;
status: "INCOMPLETE" | "QUEUED" | "RUNNING" | "COMPLETED" | "FAILED";
status:
| "INCOMPLETE"
| "QUEUED"
| "RUNNING"
| "COMPLETED"
| "TERMINATED"
| "FAILED";
input_data: { [key: string]: any };
output_data: { [key: string]: Array<any> };
add_time: Date;

View File

@ -1,12 +0,0 @@
DB_USER=postgres
DB_PASS=your-super-secret-and-long-postgres-password
DB_NAME=postgres
DB_PORT=5432
DATABASE_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/${DB_NAME}?connect_timeout=60&schema=market"
SENTRY_DSN=https://11d0640fef35640e0eb9f022eb7d7626@o4505260022104064.ingest.us.sentry.io/4507890252447744
ENABLE_AUTH=true
SUPABASE_JWT_SECRET=our-super-secret-jwt-token-with-at-least-32-characters-long
BACKEND_CORS_ALLOW_ORIGINS="http://localhost:3000,http://127.0.0.1:3000"
APP_ENV=local

View File

@ -1,6 +0,0 @@
database.db
database.db-journal
build/
config.json
secrets/*
!secrets/.gitkeep

View File

@ -1,72 +0,0 @@
FROM python:3.11.10-slim-bookworm AS builder
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
WORKDIR /app
RUN echo 'Acquire::http::Pipeline-Depth 0;\nAcquire::http::No-Cache true;\nAcquire::BrokenProxy true;\n' > /etc/apt/apt.conf.d/99fixbadproxy
RUN apt-get update --allow-releaseinfo-change --fix-missing
# Install build dependencies
RUN apt-get install -y build-essential
RUN apt-get install -y libpq5
RUN apt-get install -y libz-dev
RUN apt-get install -y libssl-dev
ENV POETRY_VERSION=1.8.3 \
POETRY_HOME="/opt/poetry" \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false
ENV PATH="$POETRY_HOME/bin:$PATH"
# Upgrade pip and setuptools to fix security vulnerabilities
RUN pip3 install --upgrade pip setuptools
RUN pip3 install poetry
# Copy and install dependencies
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
COPY autogpt_platform/market/poetry.lock autogpt_platform/market/pyproject.toml /app/autogpt_platform/market/
WORKDIR /app/autogpt_platform/market
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi
# Generate Prisma client
COPY autogpt_platform/market /app/autogpt_platform/market
RUN poetry config virtualenvs.create false \
&& poetry run prisma generate
FROM python:3.11.10-slim-bookworm AS server_dependencies
WORKDIR /app
# Upgrade pip and setuptools to fix security vulnerabilities
RUN pip3 install --upgrade pip setuptools
# Copy only necessary files from builder
COPY --from=builder /app /app
COPY --from=builder /usr/local/lib/python3.11 /usr/local/lib/python3.11
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy Prisma binaries
COPY --from=builder /root/.cache/prisma-python/binaries /root/.cache/prisma-python/binaries
ENV PATH="/app/.venv/bin:$PATH"
RUN mkdir -p /app/autogpt_platform/autogpt_libs
RUN mkdir -p /app/autogpt_platform/market
COPY autogpt_platform/autogpt_libs /app/autogpt_platform/autogpt_libs
COPY autogpt_platform/market /app/autogpt_platform/market
WORKDIR /app/autogpt_platform/market
FROM server_dependencies AS server
ENV DATABASE_URL=""
ENV PORT=8015
CMD ["poetry", "run", "app"]

View File

@ -1,37 +0,0 @@
# AutoGPT Agent Marketplace
## Overview
AutoGPT Agent Marketplace is an open-source platform for autonomous AI agents. This project aims to create a user-friendly, accessible marketplace where users can discover, utilize, and contribute to a diverse ecosystem of AI solutions.
## Vision
Our vision is to empower users with customizable and free AI agents, fostering an open-source community that drives innovation in AI automation across various industries.
## Key Features
- Agent Discovery and Search
- Agent Listings with Detailed Information
- User Profiles
- Data Protection and Compliance
## Getting Started
To get started with the AutoGPT Agent Marketplace, follow these steps:
- Copy `.env.example` to `.env` and fill in the required environment variables
- Run `poetry run setup`
- Run `poetry run populate`
- Run `poetry run app`
## Poetry Run Commands
This section outlines the available command line scripts for this project, configured using Poetry. You can execute these scripts directly using Poetry. Each command performs a specific operation as described below:
- `poetry run format`: Runs the formatting script to ensure code consistency.
- `poetry run lint`: Executes the linting script to identify and fix potential code issues.
- `poetry run app`: Starts the main application.
- `poetry run setup`: Runs the setup script to configure the database.
- `poetry run populate`: Populates the database with initial data using the specified script.
To run any of these commands, ensure Poetry is installed on your system and execute the commands from the project's root directory.

View File

@ -1,16 +0,0 @@
version: "3"
services:
postgres:
image: ankane/pgvector:latest
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_DB: ${DB_NAME}
PGUSER: ${DB_USER}
healthcheck:
test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB
interval: 10s
timeout: 5s
retries: 5
ports:
- "${DB_PORT}:5432"

View File

@ -1,97 +0,0 @@
import contextlib
import logging.config
import os
import dotenv
import fastapi
import fastapi.middleware.cors
import fastapi.middleware.gzip
import prisma
import prometheus_fastapi_instrumentator
import sentry_sdk
import sentry_sdk.integrations.asyncio
import sentry_sdk.integrations.fastapi
import sentry_sdk.integrations.starlette
import market.config
import market.routes.admin
import market.routes.agents
import market.routes.analytics
import market.routes.search
import market.routes.submissions
dotenv.load_dotenv()
logging.config.dictConfig(market.config.LogConfig().model_dump())
if os.environ.get("SENTRY_DSN"):
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"),
traces_sample_rate=1.0,
profiles_sample_rate=1.0,
enable_tracing=True,
environment=os.environ.get("RUN_ENV", default="CLOUD").lower(),
integrations=[
sentry_sdk.integrations.starlette.StarletteIntegration(
transaction_style="url"
),
sentry_sdk.integrations.fastapi.FastApiIntegration(transaction_style="url"),
sentry_sdk.integrations.asyncio.AsyncioIntegration(),
],
)
db_client = prisma.Prisma(auto_register=True)
@contextlib.asynccontextmanager
async def lifespan(app: fastapi.FastAPI):
await db_client.connect()
yield
await db_client.disconnect()
docs_url = "/docs"
app = fastapi.FastAPI(
title="Marketplace API",
description="AutoGPT Marketplace API is a service that allows users to share AI agents.",
summary="Maketplace API",
version="0.1",
lifespan=lifespan,
root_path="/api/v1/market",
docs_url=docs_url,
)
app.add_middleware(fastapi.middleware.gzip.GZipMiddleware, minimum_size=1000)
app.add_middleware(
middleware_class=fastapi.middleware.cors.CORSMiddleware,
allow_origins=os.environ.get(
"BACKEND_CORS_ALLOW_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000"
).split(","),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(market.routes.agents.router, tags=["agents"])
app.include_router(market.routes.search.router, tags=["search"])
app.include_router(market.routes.submissions.router, tags=["submissions"])
app.include_router(market.routes.admin.router, prefix="/admin", tags=["admin"])
app.include_router(
market.routes.analytics.router, prefix="/analytics", tags=["analytics"]
)
@app.get("/health")
def health():
return fastapi.responses.HTMLResponse(
content="<h1>Marketplace API</h1>", status_code=200
)
@app.get("/")
def default():
return fastapi.responses.HTMLResponse(
content="<h1>Marketplace API</h1>", status_code=200
)
prometheus_fastapi_instrumentator.Instrumentator().instrument(app).expose(app)

View File

@ -1,30 +0,0 @@
from pydantic import BaseModel
class LogConfig(BaseModel):
"""Logging configuration to be set for the server"""
LOGGER_NAME: str = "marketplace"
LOG_FORMAT: str = "%(levelprefix)s | %(asctime)s | %(message)s"
LOG_LEVEL: str = "DEBUG"
# Logging config
version: int = 1
disable_existing_loggers: bool = False
formatters: dict = {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
}
handlers: dict = {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
}
loggers: dict = {
LOGGER_NAME: {"handlers": ["default"], "level": LOG_LEVEL},
}

View File

@ -1,725 +0,0 @@
import datetime
import typing
import fuzzywuzzy.fuzz
import prisma.enums
import prisma.errors
import prisma.models
import prisma.types
import pydantic
import market.model
import market.utils.extension_types
class AgentQueryError(Exception):
"""Custom exception for agent query errors"""
pass
class TopAgentsDBResponse(pydantic.BaseModel):
"""
Represents a response containing a list of top agents.
Attributes:
analytics (list[AgentResponse]): The list of top agents.
total_count (int): The total count of agents.
page (int): The current page number.
page_size (int): The number of agents per page.
total_pages (int): The total number of pages.
"""
analytics: list[prisma.models.AnalyticsTracker]
total_count: int
page: int
page_size: int
total_pages: int
class FeaturedAgentResponse(pydantic.BaseModel):
"""
Represents a response containing a list of featured agents.
Attributes:
featured_agents (list[FeaturedAgent]): The list of featured agents.
total_count (int): The total count of featured agents.
page (int): The current page number.
page_size (int): The number of agents per page.
total_pages (int): The total number of pages.
"""
featured_agents: list[prisma.models.FeaturedAgent]
total_count: int
page: int
page_size: int
total_pages: int
async def delete_agent(agent_id: str) -> prisma.models.Agents | None:
"""
Delete an agent from the database.
Args:
agent_id (str): The ID of the agent to delete.
Returns:
prisma.models.Agents | None: The deleted agent if found, None otherwise.
Raises:
AgentQueryError: If there is an error deleting the agent from the database.
"""
try:
deleted_agent = await prisma.models.Agents.prisma().delete(
where={"id": agent_id}
)
return deleted_agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def create_agent_entry(
name: str,
description: str,
author: str,
keywords: typing.List[str],
categories: typing.List[str],
graph: prisma.Json,
submission_state: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.PENDING,
):
"""
Create a new agent entry in the database.
Args:
name (str): The name of the agent.
description (str): The description of the agent.
author (str): The author of the agent.
keywords (List[str]): The keywords associated with the agent.
categories (List[str]): The categories associated with the agent.
graph (dict): The graph data of the agent.
Returns:
dict: The newly created agent entry.
Raises:
AgentQueryError: If there is an error creating the agent entry.
"""
try:
agent = await prisma.models.Agents.prisma().create(
data={
"name": name,
"description": description,
"author": author,
"keywords": keywords,
"categories": categories,
"graph": graph,
"AnalyticsTracker": {"create": {"downloads": 0, "views": 0}},
"submissionStatus": submission_state,
}
)
return agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def update_agent_entry(
agent_id: str,
version: int,
submission_state: prisma.enums.SubmissionStatus,
comments: str | None = None,
) -> prisma.models.Agents | None:
"""
Update an existing agent entry in the database.
Args:
agent_id (str): The ID of the agent.
version (int): The version of the agent.
submission_state (prisma.enums.SubmissionStatus): The submission state of the agent.
"""
try:
agent = await prisma.models.Agents.prisma().update(
where={"id": agent_id},
data={
"version": version,
"submissionStatus": submission_state,
"submissionReviewDate": datetime.datetime.now(datetime.timezone.utc),
"submissionReviewComments": comments,
},
)
return agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Agent Update Failed Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_agents(
page: int = 1,
page_size: int = 10,
name: str | None = None,
keyword: str | None = None,
category: str | None = None,
description: str | None = None,
description_threshold: int = 60,
submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED,
sort_by: str = "createdAt",
sort_order: typing.Literal["desc"] | typing.Literal["asc"] = "desc",
):
"""
Retrieve a list of agents from the database based on the provided filters and pagination parameters.
Args:
page (int, optional): The page number to retrieve. Defaults to 1.
page_size (int, optional): The number of agents per page. Defaults to 10.
name (str, optional): Filter agents by name. Defaults to None.
keyword (str, optional): Filter agents by keyword. Defaults to None.
category (str, optional): Filter agents by category. Defaults to None.
description (str, optional): Filter agents by description. Defaults to None.
description_threshold (int, optional): The minimum fuzzy search threshold for the description. Defaults to 60.
sort_by (str, optional): The field to sort the agents by. Defaults to "createdAt".
sort_order (str, optional): The sort order ("asc" or "desc"). Defaults to "desc".
Returns:
dict: A dictionary containing the list of agents, total count, current page number, page size, and total number of pages.
"""
try:
# Define the base query
query = {}
# Add optional filters
if name:
query["name"] = {"contains": name, "mode": "insensitive"}
if keyword:
query["keywords"] = {"has": keyword}
if category:
query["categories"] = {"has": category}
query["submissionStatus"] = submission_status
# Define sorting
order = {sort_by: sort_order}
# Calculate pagination
skip = (page - 1) * page_size
# Execute the query
try:
agents = await prisma.models.Agents.prisma().find_many(
where=query, # type: ignore
order=order, # type: ignore
skip=skip,
take=page_size,
)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
# Apply fuzzy search on description if provided
if description:
try:
filtered_agents = []
for agent in agents:
if (
agent.description
and fuzzywuzzy.fuzz.partial_ratio(
description.lower(), agent.description.lower()
)
>= description_threshold
):
filtered_agents.append(agent)
agents = filtered_agents
except AttributeError as e:
raise AgentQueryError(f"Error during fuzzy search: {str(e)}")
# Get total count for pagination info
total_count = len(agents)
return {
"agents": agents,
"total_count": total_count,
"page": page,
"page_size": page_size,
"total_pages": (total_count + page_size - 1) // page_size,
}
except AgentQueryError as e:
# Log the error or handle it as needed
raise e
except ValueError as e:
raise AgentQueryError(f"Invalid input parameter: {str(e)}")
except Exception as e:
# Catch any other unexpected exceptions
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_agent_details(agent_id: str, version: int | None = None):
"""
Retrieve agent details from the database.
Args:
agent_id (str): The ID of the agent.
version (int | None, optional): The version of the agent. Defaults to None.
Returns:
dict: The agent details.
Raises:
AgentQueryError: If the agent is not found or if there is an error querying the database.
"""
try:
query = {"id": agent_id}
if version is not None:
query["version"] = version # type: ignore
agent = await prisma.models.Agents.prisma().find_first(where=query) # type: ignore
if not agent:
raise AgentQueryError("Agent not found")
return agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def search_db(
query: str,
page: int = 1,
page_size: int = 10,
categories: typing.List[str] | None = None,
description_threshold: int = 60,
sort_by: str = "rank",
sort_order: typing.Literal["desc"] | typing.Literal["asc"] = "desc",
submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED,
) -> market.model.ListResponse[market.utils.extension_types.AgentsWithRank]:
"""Perform a search for agents based on the provided query string.
Args:
query (str): the search string
page (int, optional): page for searching. Defaults to 1.
page_size (int, optional): the number of results to return. Defaults to 10.
categories (List[str] | None, optional): list of category filters. Defaults to None.
description_threshold (int, optional): number of characters to return. Defaults to 60.
sort_by (str, optional): sort by option. Defaults to "rank".
sort_order ("asc" | "desc", optional): the sort order. Defaults to "desc".
Raises:
AgentQueryError: Raises an error if the query fails.
AgentQueryError: Raises if an unexpected error occurs.
Returns:
List[AgentsWithRank]: List of agents matching the search criteria.
"""
try:
offset = (page - 1) * page_size
category_filter = "1=1"
if categories:
category_conditions = [f"'{cat}' = ANY(categories)" for cat in categories]
category_filter = "AND (" + " OR ".join(category_conditions) + ")"
# Construct the ORDER BY clause based on the sort_by parameter
if sort_by in ["createdAt", "updatedAt"]:
order_by_clause = f'"{sort_by}" {sort_order.upper()}, rank DESC'
elif sort_by == "name":
order_by_clause = f"name {sort_order.upper()}, rank DESC"
else:
order_by_clause = 'rank DESC, "createdAt" DESC'
submission_status_filter = f""""submissionStatus" = '{submission_status}'"""
sql_query = f"""
WITH query AS (
SELECT to_tsquery(string_agg(lexeme || ':*', ' & ' ORDER BY positions)) AS q
FROM unnest(to_tsvector('{query}'))
)
SELECT
id,
"createdAt",
"updatedAt",
version,
name,
LEFT(description, {description_threshold}) AS description,
author,
keywords,
categories,
graph,
"submissionStatus",
"submissionDate",
CASE
WHEN query.q::text = '' THEN 1.0
ELSE COALESCE(ts_rank(CAST(search AS tsvector), query.q), 0.0)
END AS rank
FROM market."Agents", query
WHERE
(query.q::text = '' OR search @@ query.q)
AND {category_filter}
AND {submission_status_filter}
ORDER BY {order_by_clause}
LIMIT {page_size}
OFFSET {offset};
"""
results = await prisma.client.get_client().query_raw(
query=sql_query,
model=market.utils.extension_types.AgentsWithRank,
)
class CountResponse(pydantic.BaseModel):
count: int
count_query = f"""
WITH query AS (
SELECT to_tsquery(string_agg(lexeme || ':*', ' & ' ORDER BY positions)) AS q
FROM unnest(to_tsvector('{query}'))
)
SELECT COUNT(*)
FROM market."Agents", query
WHERE (search @@ query.q OR query.q = '') AND {category_filter} AND {submission_status_filter};
"""
total_count = await prisma.client.get_client().query_first(
query=count_query,
model=CountResponse,
)
total_count = total_count.count if total_count else 0
return market.model.ListResponse(
items=results,
total_count=total_count,
page=page,
page_size=page_size,
total_pages=(total_count + page_size - 1) // page_size,
)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_top_agents_by_downloads(
page: int = 1,
page_size: int = 10,
submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED,
) -> market.model.ListResponse[prisma.models.AnalyticsTracker]:
"""Retrieve the top agents by download count.
Args:
page (int, optional): The page number. Defaults to 1.
page_size (int, optional): The number of agents per page. Defaults to 10.
Returns:
dict: A dictionary containing the list of agents, total count, current page number, page size, and total number of pages.
"""
try:
# Calculate pagination
skip = (page - 1) * page_size
# Execute the query
try:
# Agents with no downloads will not be included in the results... is this the desired behavior?
analytics = await prisma.models.AnalyticsTracker.prisma().find_many(
include={"agent": True},
order={"downloads": "desc"},
where={"agent": {"is": {"submissionStatus": submission_status}}},
skip=skip,
take=page_size,
)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
try:
total_count = await prisma.models.AnalyticsTracker.prisma().count(
where={"agent": {"is": {"submissionStatus": submission_status}}},
)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
return market.model.ListResponse(
items=analytics,
total_count=total_count,
page=page,
page_size=page_size,
total_pages=(total_count + page_size - 1) // page_size,
)
except AgentQueryError as e:
# Log the error or handle it as needed
raise e from e
except ValueError as e:
raise AgentQueryError(f"Invalid input parameter: {str(e)}") from e
except Exception as e:
# Catch any other unexpected exceptions
raise AgentQueryError(f"Unexpected error occurred: {str(e)}") from e
async def set_agent_featured(
agent_id: str, is_active: bool = True, featured_categories: list[str] = ["featured"]
) -> prisma.models.FeaturedAgent:
"""Set an agent as featured in the database.
Args:
agent_id (str): The ID of the agent.
category (str, optional): The category to set the agent as featured. Defaults to "featured".
Raises:
AgentQueryError: If there is an error setting the agent as featured.
"""
try:
agent = await prisma.models.Agents.prisma().find_unique(where={"id": agent_id})
if not agent:
raise AgentQueryError(f"Agent with ID {agent_id} not found.")
featured = await prisma.models.FeaturedAgent.prisma().upsert(
where={"agentId": agent_id},
data={
"update": {
"featuredCategories": featured_categories,
"isActive": is_active,
},
"create": {
"featuredCategories": featured_categories,
"isActive": is_active,
"agent": {"connect": {"id": agent_id}},
},
},
)
return featured
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_featured_agents(
category: str = "featured",
page: int = 1,
page_size: int = 10,
submission_status: prisma.enums.SubmissionStatus = prisma.enums.SubmissionStatus.APPROVED,
) -> FeaturedAgentResponse:
"""Retrieve a list of featured agents from the database based on the provided category.
Args:
category (str, optional): The category of featured agents to retrieve. Defaults to "featured".
page (int, optional): The page number to retrieve. Defaults to 1.
page_size (int, optional): The number of agents per page. Defaults to 10.
Returns:
dict: A dictionary containing the list of featured agents, total count, current page number, page size, and total number of pages.
"""
try:
# Calculate pagination
skip = (page - 1) * page_size
# Execute the query
try:
featured_agents = await prisma.models.FeaturedAgent.prisma().find_many(
where={
"featuredCategories": {"has": category},
"isActive": True,
"agent": {"is": {"submissionStatus": submission_status}},
},
include={"agent": {"include": {"AnalyticsTracker": True}}},
skip=skip,
take=page_size,
)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
# Get total count for pagination info
total_count = len(featured_agents)
return FeaturedAgentResponse(
featured_agents=featured_agents,
total_count=total_count,
page=page,
page_size=page_size,
total_pages=(total_count + page_size - 1) // page_size,
)
except AgentQueryError as e:
# Log the error or handle it as needed
raise e from e
except ValueError as e:
raise AgentQueryError(f"Invalid input parameter: {str(e)}") from e
except Exception as e:
# Catch any other unexpected exceptions
raise AgentQueryError(f"Unexpected error occurred: {str(e)}") from e
async def remove_featured_category(
agent_id: str, category: str
) -> prisma.models.FeaturedAgent | None:
"""Adds a featured category to an agent.
Args:
agent_id (str): The ID of the agent.
category (str): The category to add to the agent.
Returns:
FeaturedAgentResponse: The updated list of featured agents.
"""
try:
# get the existing categories
featured_agent = await prisma.models.FeaturedAgent.prisma().find_unique(
where={"agentId": agent_id},
include={"agent": True},
)
if not featured_agent:
raise AgentQueryError(f"Agent with ID {agent_id} not found.")
# remove the category from the list
featured_agent.featuredCategories.remove(category)
featured_agent = await prisma.models.FeaturedAgent.prisma().update(
where={"agentId": agent_id},
data={"featuredCategories": featured_agent.featuredCategories},
)
return featured_agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def add_featured_category(
agent_id: str, category: str
) -> prisma.models.FeaturedAgent | None:
"""Removes a featured category from an agent.
Args:
agent_id (str): The ID of the agent.
category (str): The category to remove from the agent.
Returns:
FeaturedAgentResponse: The updated list of featured agents.
"""
try:
featured_agent = await prisma.models.FeaturedAgent.prisma().update(
where={"agentId": agent_id},
data={"featuredCategories": {"push": [category]}},
)
return featured_agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_agent_featured(agent_id: str) -> prisma.models.FeaturedAgent | None:
"""Retrieve an agent's featured categories from the database.
Args:
agent_id (str): The ID of the agent.
Returns:
FeaturedAgentResponse: The list of featured agents.
"""
try:
featured_agent = await prisma.models.FeaturedAgent.prisma().find_unique(
where={"agentId": agent_id},
)
return featured_agent
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_not_featured_agents(
page: int = 1, page_size: int = 10
) -> typing.List[prisma.models.Agents]:
"""
Retrieve a list of not featured agents from the database.
"""
try:
agents = await prisma.client.get_client().query_raw(
query=f"""
SELECT
"market"."Agents".id,
"market"."Agents"."createdAt",
"market"."Agents"."updatedAt",
"market"."Agents".version,
"market"."Agents".name,
LEFT("market"."Agents".description, 500) AS description,
"market"."Agents".author,
"market"."Agents".keywords,
"market"."Agents".categories,
"market"."Agents".graph,
"market"."Agents"."submissionStatus",
"market"."Agents"."submissionDate",
"market"."Agents".search::text AS search
FROM "market"."Agents"
LEFT JOIN "market"."FeaturedAgent" ON "market"."Agents"."id" = "market"."FeaturedAgent"."agentId"
WHERE ("market"."FeaturedAgent"."agentId" IS NULL OR "market"."FeaturedAgent"."featuredCategories" = '{{}}')
AND "market"."Agents"."submissionStatus" = 'APPROVED'
ORDER BY "market"."Agents"."createdAt" DESC
LIMIT {page_size} OFFSET {page_size * (page - 1)}
""",
model=prisma.models.Agents,
)
return agents
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")
async def get_all_categories() -> market.model.CategoriesResponse:
"""
Retrieve all unique categories from the database.
Returns:
CategoriesResponse: A list of unique categories.
"""
try:
agents = await prisma.models.Agents.prisma().find_many(distinct=["categories"])
# Aggregate categories on the Python side
all_categories = set()
for agent in agents:
all_categories.update(agent.categories)
unique_categories = sorted(list(all_categories))
return market.model.CategoriesResponse(unique_categories=unique_categories)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception:
# Return an empty list of categories in case of unexpected errors
return market.model.CategoriesResponse(unique_categories=[])
async def create_agent_installed_event(
event_data: market.model.AgentInstalledFromMarketplaceEventData,
):
try:
await prisma.models.InstallTracker.prisma().create(
data={
"installedAgentId": event_data.installed_agent_id,
"marketplaceAgentId": event_data.marketplace_agent_id,
"installationLocation": prisma.enums.InstallationLocation(
event_data.installation_location.name
),
}
)
except prisma.errors.PrismaError as e:
raise AgentQueryError(f"Database query failed: {str(e)}")
except Exception as e:
raise AgentQueryError(f"Unexpected error occurred: {str(e)}")

View File

@ -1,161 +0,0 @@
import datetime
import typing
from enum import Enum
from typing import Generic, Literal, TypeVar, Union
import prisma.enums
import pydantic
class InstallationLocation(str, Enum):
LOCAL = "local"
CLOUD = "cloud"
class AgentInstalledFromMarketplaceEventData(pydantic.BaseModel):
marketplace_agent_id: str
installed_agent_id: str
installation_location: InstallationLocation
class AgentInstalledFromTemplateEventData(pydantic.BaseModel):
template_id: str
installed_agent_id: str
installation_location: InstallationLocation
class AgentInstalledFromMarketplaceEvent(pydantic.BaseModel):
event_name: Literal["agent_installed_from_marketplace"]
event_data: AgentInstalledFromMarketplaceEventData
class AgentInstalledFromTemplateEvent(pydantic.BaseModel):
event_name: Literal["agent_installed_from_template"]
event_data: AgentInstalledFromTemplateEventData
AnalyticsEvent = Union[
AgentInstalledFromMarketplaceEvent, AgentInstalledFromTemplateEvent
]
class AnalyticsRequest(pydantic.BaseModel):
event: AnalyticsEvent
class AddAgentRequest(pydantic.BaseModel):
graph: dict[str, typing.Any]
author: str
keywords: list[str]
categories: list[str]
class SubmissionReviewRequest(pydantic.BaseModel):
agent_id: str
version: int
status: prisma.enums.SubmissionStatus
comments: str | None
class AgentResponse(pydantic.BaseModel):
"""
Represents a response from an agent.
Attributes:
id (str): The ID of the agent.
name (str, optional): The name of the agent.
description (str, optional): The description of the agent.
author (str, optional): The author of the agent.
keywords (list[str]): The keywords associated with the agent.
categories (list[str]): The categories the agent belongs to.
version (int): The version of the agent.
createdAt (str): The creation date of the agent.
updatedAt (str): The last update date of the agent.
"""
id: str
name: typing.Optional[str]
description: typing.Optional[str]
author: typing.Optional[str]
keywords: list[str]
categories: list[str]
version: int
createdAt: datetime.datetime
updatedAt: datetime.datetime
submissionStatus: str
views: int = 0
downloads: int = 0
class AgentDetailResponse(pydantic.BaseModel):
"""
Represents the response data for an agent detail.
Attributes:
id (str): The ID of the agent.
name (Optional[str]): The name of the agent.
description (Optional[str]): The description of the agent.
author (Optional[str]): The author of the agent.
keywords (List[str]): The keywords associated with the agent.
categories (List[str]): The categories the agent belongs to.
version (int): The version of the agent.
createdAt (str): The creation date of the agent.
updatedAt (str): The last update date of the agent.
graph (Dict[str, Any]): The graph data of the agent.
"""
id: str
name: typing.Optional[str]
description: typing.Optional[str]
author: typing.Optional[str]
keywords: list[str]
categories: list[str]
version: int
createdAt: datetime.datetime
updatedAt: datetime.datetime
graph: dict[str, typing.Any]
class FeaturedAgentResponse(pydantic.BaseModel):
"""
Represents the response data for an agent detail.
"""
agentId: str
featuredCategories: list[str]
createdAt: datetime.datetime
updatedAt: datetime.datetime
isActive: bool
class CategoriesResponse(pydantic.BaseModel):
"""
Represents the response data for a list of categories.
Attributes:
unique_categories (list[str]): The list of unique categories.
"""
unique_categories: list[str]
T = TypeVar("T")
class ListResponse(pydantic.BaseModel, Generic[T]):
"""
Represents a list response.
Attributes:
items (list[T]): The list of items.
total_count (int): The total count of items.
page (int): The current page number.
page_size (int): The number of items per page.
total_pages (int): The total number of pages.
"""
items: list[T]
total_count: int
page: int
page_size: int
total_pages: int

View File

@ -1,286 +0,0 @@
import logging
import typing
import autogpt_libs.auth
import fastapi
import prisma
import prisma.enums
import prisma.models
import market.db
import market.model
logger = logging.getLogger("marketplace")
router = fastapi.APIRouter()
@router.delete("/agent/{agent_id}", response_model=market.model.AgentResponse)
async def delete_agent(
agent_id: str,
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
):
"""
Delete an agent and all related records from the database.
Args:
agent_id (str): The ID of the agent to delete.
Returns:
market.model.AgentResponse: The deleted agent's data.
Raises:
fastapi.HTTPException: If the agent is not found or if there's an error during deletion.
"""
try:
deleted_agent = await market.db.delete_agent(agent_id)
if deleted_agent:
return market.model.AgentResponse(**deleted_agent.dict())
else:
raise fastapi.HTTPException(status_code=404, detail="Agent not found")
except market.db.AgentQueryError as e:
logger.error(f"Error deleting agent: {e}")
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error deleting agent: {e}")
raise fastapi.HTTPException(
status_code=500, detail="An unexpected error occurred"
)
@router.post("/agent", response_model=market.model.AgentResponse)
async def create_agent_entry(
request: market.model.AddAgentRequest,
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
):
"""
A basic endpoint to create a new agent entry in the database.
"""
try:
agent = await market.db.create_agent_entry(
request.graph["name"],
request.graph["description"],
request.author,
request.keywords,
request.categories,
prisma.Json(request.graph),
)
return fastapi.responses.PlainTextResponse(agent.model_dump_json())
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
@router.post("/agent/featured/{agent_id}")
async def set_agent_featured(
agent_id: str,
categories: list[str] = fastapi.Query(
default=["featured"],
description="The categories to set the agent as featured in",
),
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
) -> market.model.FeaturedAgentResponse:
"""
A basic endpoint to set an agent as featured in the database.
"""
try:
agent = await market.db.set_agent_featured(
agent_id, is_active=True, featured_categories=categories
)
return market.model.FeaturedAgentResponse(**agent.model_dump())
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
@router.get("/agent/featured/{agent_id}")
async def get_agent_featured(
agent_id: str,
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
) -> market.model.FeaturedAgentResponse | None:
"""
A basic endpoint to get an agent as featured in the database.
"""
try:
agent = await market.db.get_agent_featured(agent_id)
if agent:
return market.model.FeaturedAgentResponse(**agent.model_dump())
else:
return None
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
@router.delete("/agent/featured/{agent_id}")
async def unset_agent_featured(
agent_id: str,
category: str = "featured",
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
) -> market.model.FeaturedAgentResponse | None:
"""
A basic endpoint to unset an agent as featured in the database.
"""
try:
featured = await market.db.remove_featured_category(agent_id, category=category)
if featured:
return market.model.FeaturedAgentResponse(**featured.model_dump())
else:
return None
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
@router.get("/agent/not-featured")
async def get_not_featured_agents(
page: int = fastapi.Query(1, ge=1, description="Page number"),
page_size: int = fastapi.Query(
10, ge=1, le=100, description="Number of items per page"
),
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
) -> market.model.ListResponse[market.model.AgentResponse]:
"""
A basic endpoint to get all not featured agents in the database.
"""
try:
agents = await market.db.get_not_featured_agents(page=page, page_size=page_size)
return market.model.ListResponse(
items=[
market.model.AgentResponse(**agent.model_dump()) for agent in agents
],
total_count=len(agents),
page=page,
page_size=page_size,
total_pages=999,
)
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
@router.get(
"/agent/submissions",
response_model=market.model.ListResponse[market.model.AgentResponse],
)
async def get_agent_submissions(
page: int = fastapi.Query(1, ge=1, description="Page number"),
page_size: int = fastapi.Query(
10, ge=1, le=100, description="Number of items per page"
),
name: typing.Optional[str] = fastapi.Query(
None, description="Filter by agent name"
),
keyword: typing.Optional[str] = fastapi.Query(
None, description="Filter by keyword"
),
category: typing.Optional[str] = fastapi.Query(
None, description="Filter by category"
),
description: typing.Optional[str] = fastapi.Query(
None, description="Fuzzy search in description"
),
description_threshold: int = fastapi.Query(
60, ge=0, le=100, description="Fuzzy search threshold"
),
sort_by: str = fastapi.Query("createdAt", description="Field to sort by"),
sort_order: typing.Literal["asc", "desc"] = fastapi.Query(
"desc", description="Sort order (asc or desc)"
),
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
) -> market.model.ListResponse[market.model.AgentResponse]:
logger.info("Getting agent submissions")
try:
result = await market.db.get_agents(
page=page,
page_size=page_size,
name=name,
keyword=keyword,
category=category,
description=description,
description_threshold=description_threshold,
sort_by=sort_by,
sort_order=sort_order,
submission_status=prisma.enums.SubmissionStatus.PENDING,
)
agents = [
market.model.AgentResponse(**agent.dict()) for agent in result["agents"]
]
return market.model.ListResponse(
items=agents,
total_count=result["total_count"],
page=result["page"],
page_size=result["page_size"],
total_pages=result["total_pages"],
)
except market.db.AgentQueryError as e:
logger.error(f"Error getting agent submissions: {e}")
raise fastapi.HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Error getting agent submissions: {e}")
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {e}"
)
@router.post("/agent/submissions")
async def review_submission(
review_request: market.model.SubmissionReviewRequest,
user: autogpt_libs.auth.User = fastapi.Depends(
autogpt_libs.auth.requires_admin_user
),
) -> prisma.models.Agents | None:
"""
A basic endpoint to review a submission in the database.
"""
logger.info(
f"Reviewing submission: {review_request.agent_id}, {review_request.version}"
)
try:
agent = await market.db.update_agent_entry(
agent_id=review_request.agent_id,
version=review_request.version,
submission_state=review_request.status,
comments=review_request.comments,
)
return agent
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
@router.get("/categories")
async def get_categories() -> market.model.CategoriesResponse:
"""
A basic endpoint to get all available categories.
"""
try:
categories = await market.db.get_all_categories()
return categories
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))

View File

@ -1,76 +0,0 @@
import datetime
from unittest import mock
import autogpt_libs.auth.middleware
import fastapi
import fastapi.testclient
import prisma.enums
import prisma.models
import market.app
client = fastapi.testclient.TestClient(market.app.app)
async def override_auth_middleware(request: fastapi.Request):
return {"sub": "3e53486c-cf57-477e-ba2a-cb02dc828e1a", "role": "admin"}
market.app.app.dependency_overrides[autogpt_libs.auth.middleware.auth_middleware] = (
override_auth_middleware
)
def test_get_submissions():
with mock.patch("market.db.get_agents") as mock_get_agents:
mock_get_agents.return_value = {
"agents": [],
"total_count": 0,
"page": 1,
"page_size": 10,
"total_pages": 0,
}
response = client.get(
"/api/v1/market/admin/agent/submissions?page=1&page_size=10&description_threshold=60&sort_by=createdAt&sort_order=desc",
headers={"Bearer": ""},
)
assert response.status_code == 200
assert response.json() == {
"agents": [],
"total_count": 0,
"page": 1,
"page_size": 10,
"total_pages": 0,
}
def test_review_submission():
with mock.patch("market.db.update_agent_entry") as mock_update_agent_entry:
mock_update_agent_entry.return_value = prisma.models.Agents(
id="aaa-bbb-ccc",
version=1,
createdAt=datetime.datetime.fromisoformat("2021-10-01T00:00:00+00:00"),
updatedAt=datetime.datetime.fromisoformat("2021-10-01T00:00:00+00:00"),
submissionStatus=prisma.enums.SubmissionStatus.APPROVED,
submissionDate=datetime.datetime.fromisoformat("2021-10-01T00:00:00+00:00"),
submissionReviewComments="Looks good",
submissionReviewDate=datetime.datetime.fromisoformat(
"2021-10-01T00:00:00+00:00"
),
keywords=["test"],
categories=["test"],
graph='{"name": "test", "description": "test"}', # type: ignore
)
response = client.post(
"/api/v1/market/admin/agent/submissions",
headers={
"Authorization": "Bearer token"
}, # Assuming you need an authorization token
json={
"agent_id": "aaa-bbb-ccc",
"version": 1,
"status": "APPROVED",
"comments": "Looks good",
},
)
assert response.status_code == 200

View File

@ -1,368 +0,0 @@
import json
import tempfile
import typing
import fastapi
import fastapi.responses
import prisma
import prisma.enums
import market.db
import market.model
import market.utils.analytics
router = fastapi.APIRouter()
@router.get(
"/agents", response_model=market.model.ListResponse[market.model.AgentResponse]
)
async def list_agents(
page: int = fastapi.Query(1, ge=1, description="Page number"),
page_size: int = fastapi.Query(
10, ge=1, le=100, description="Number of items per page"
),
name: typing.Optional[str] = fastapi.Query(
None, description="Filter by agent name"
),
keyword: typing.Optional[str] = fastapi.Query(
None, description="Filter by keyword"
),
category: typing.Optional[str] = fastapi.Query(
None, description="Filter by category"
),
description: typing.Optional[str] = fastapi.Query(
None, description="Fuzzy search in description"
),
description_threshold: int = fastapi.Query(
60, ge=0, le=100, description="Fuzzy search threshold"
),
sort_by: str = fastapi.Query("createdAt", description="Field to sort by"),
sort_order: typing.Literal["asc", "desc"] = fastapi.Query(
"desc", description="Sort order (asc or desc)"
),
submission_status: prisma.enums.SubmissionStatus = fastapi.Query(
default=prisma.enums.SubmissionStatus.APPROVED,
description="Filter by submission status",
),
):
"""
Retrieve a list of agents based on the provided filters.
Args:
page (int): Page number (default: 1).
page_size (int): Number of items per page (default: 10, min: 1, max: 100).
name (str, optional): Filter by agent name.
keyword (str, optional): Filter by keyword.
category (str, optional): Filter by category.
description (str, optional): Fuzzy search in description.
description_threshold (int): Fuzzy search threshold (default: 60, min: 0, max: 100).
sort_by (str): Field to sort by (default: "createdAt").
sort_order (str): Sort order (asc or desc) (default: "desc").
submission_status (str): Filter by submission status (default: "APPROVED").
Returns:
market.model.ListResponse[market.model.AgentResponse]: A response containing the list of agents and pagination information.
Raises:
HTTPException: If there is a client error (status code 400) or an unexpected error (status code 500).
"""
try:
result = await market.db.get_agents(
page=page,
page_size=page_size,
name=name,
keyword=keyword,
category=category,
description=description,
description_threshold=description_threshold,
sort_by=sort_by,
sort_order=sort_order,
submission_status=submission_status,
)
agents = [
market.model.AgentResponse(**agent.dict()) for agent in result["agents"]
]
return market.model.ListResponse(
items=agents,
total_count=result["total_count"],
page=result["page"],
page_size=result["page_size"],
total_pages=result["total_pages"],
)
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {e}"
)
@router.get("/agents/{agent_id}", response_model=market.model.AgentDetailResponse)
async def get_agent_details_endpoint(
background_tasks: fastapi.BackgroundTasks,
agent_id: str = fastapi.Path(..., description="The ID of the agent to retrieve"),
version: typing.Optional[int] = fastapi.Query(
None, description="Specific version of the agent"
),
):
"""
Retrieve details of a specific agent.
Args:
agent_id (str): The ID of the agent to retrieve.
version (Optional[int]): Specific version of the agent (default: None).
Returns:
market.model.AgentDetailResponse: The response containing the agent details.
Raises:
HTTPException: If the agent is not found or an unexpected error occurs.
"""
try:
agent = await market.db.get_agent_details(agent_id, version)
background_tasks.add_task(market.utils.analytics.track_view, agent_id)
return market.model.AgentDetailResponse(**agent.model_dump())
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {str(e)}"
)
@router.get("/agents/{agent_id}/download")
async def download_agent(
background_tasks: fastapi.BackgroundTasks,
agent_id: str = fastapi.Path(..., description="The ID of the agent to retrieve"),
version: typing.Optional[int] = fastapi.Query(
None, description="Specific version of the agent"
),
):
"""
Download details of a specific agent.
NOTE: This is the same as agent details, however it also triggers
the "download" tracking. We don't actually want to download a file though
Args:
agent_id (str): The ID of the agent to retrieve.
version (Optional[int]): Specific version of the agent (default: None).
Returns:
market.model.AgentDetailResponse: The response containing the agent details.
Raises:
HTTPException: If the agent is not found or an unexpected error occurs.
"""
try:
agent = await market.db.get_agent_details(agent_id, version)
background_tasks.add_task(market.utils.analytics.track_download, agent_id)
return market.model.AgentDetailResponse(**agent.model_dump())
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=404, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {str(e)}"
)
@router.get("/agents/{agent_id}/download-file")
async def download_agent_file(
background_tasks: fastapi.BackgroundTasks,
agent_id: str = fastapi.Path(..., description="The ID of the agent to download"),
version: typing.Optional[int] = fastapi.Query(
None, description="Specific version of the agent"
),
) -> fastapi.responses.FileResponse:
"""
Download the agent file by streaming its content.
Args:
agent_id (str): The ID of the agent to download.
version (Optional[int]): Specific version of the agent to download.
Returns:
StreamingResponse: A streaming response containing the agent's graph data.
Raises:
HTTPException: If the agent is not found or an unexpected error occurs.
"""
agent = await market.db.get_agent_details(agent_id, version)
graph_data: prisma.Json = agent.graph
background_tasks.add_task(market.utils.analytics.track_download, agent_id)
file_name = f"agent_{agent_id}_v{version or 'latest'}.json"
with tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False
) as tmp_file:
tmp_file.write(json.dumps(graph_data))
tmp_file.flush()
return fastapi.responses.FileResponse(
tmp_file.name, filename=file_name, media_type="application/json"
)
# top agents by downloads
@router.get(
"/top-downloads/agents",
response_model=market.model.ListResponse[market.model.AgentResponse],
)
async def top_agents_by_downloads(
page: int = fastapi.Query(1, ge=1, description="Page number"),
page_size: int = fastapi.Query(
10, ge=1, le=100, description="Number of items per page"
),
submission_status: prisma.enums.SubmissionStatus = fastapi.Query(
default=prisma.enums.SubmissionStatus.APPROVED,
description="Filter by submission status",
),
) -> market.model.ListResponse[market.model.AgentResponse]:
"""
Retrieve a list of top agents based on the number of downloads.
Args:
page (int): Page number (default: 1).
page_size (int): Number of items per page (default: 10, min: 1, max: 100).
submission_status (str): Filter by submission status (default: "APPROVED").
Returns:
market.model.ListResponse[market.model.AgentResponse]: A response containing the list of top agents and pagination information.
Raises:
HTTPException: If there is a client error (status code 400) or an unexpected error (status code 500).
"""
try:
result = await market.db.get_top_agents_by_downloads(
page=page,
page_size=page_size,
submission_status=submission_status,
)
ret = market.model.ListResponse(
total_count=result.total_count,
page=result.page,
page_size=result.page_size,
total_pages=result.total_pages,
items=[
market.model.AgentResponse(
id=item.agent.id,
name=item.agent.name,
description=item.agent.description,
author=item.agent.author,
keywords=item.agent.keywords,
categories=item.agent.categories,
version=item.agent.version,
createdAt=item.agent.createdAt,
updatedAt=item.agent.updatedAt,
views=item.views,
downloads=item.downloads,
submissionStatus=item.agent.submissionStatus,
)
for item in result.items
if item.agent is not None
],
)
return ret
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=400, detail=str(e)) from e
except Exception as e:
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {e}"
) from e
@router.get(
"/featured/agents",
response_model=market.model.ListResponse[market.model.AgentResponse],
)
async def get_featured_agents(
category: str = fastapi.Query(
"featured", description="Category of featured agents"
),
page: int = fastapi.Query(1, ge=1, description="Page number"),
page_size: int = fastapi.Query(
10, ge=1, le=100, description="Number of items per page"
),
submission_status: prisma.enums.SubmissionStatus = fastapi.Query(
default=prisma.enums.SubmissionStatus.APPROVED,
description="Filter by submission status",
),
):
"""
Retrieve a list of featured agents based on the provided category.
Args:
category (str): Category of featured agents (default: "featured").
page (int): Page number (default: 1).
page_size (int): Number of items per page (default: 10, min: 1, max: 100).
submission_status (str): Filter by submission status (default: "APPROVED").
Returns:
market.model.ListResponse[market.model.AgentResponse]: A response containing the list of featured agents and pagination information.
Raises:
HTTPException: If there is a client error (status code 400) or an unexpected error (status code 500).
"""
try:
result = await market.db.get_featured_agents(
category=category,
page=page,
page_size=page_size,
submission_status=submission_status,
)
ret = market.model.ListResponse(
total_count=result.total_count,
page=result.page,
page_size=result.page_size,
total_pages=result.total_pages,
items=[
market.model.AgentResponse(
id=item.agent.id,
name=item.agent.name,
description=item.agent.description,
author=item.agent.author,
keywords=item.agent.keywords,
categories=item.agent.categories,
version=item.agent.version,
createdAt=item.agent.createdAt,
updatedAt=item.agent.updatedAt,
views=(
item.agent.AnalyticsTracker[0].views
if item.agent.AnalyticsTracker
and len(item.agent.AnalyticsTracker) > 0
else 0
),
downloads=(
item.agent.AnalyticsTracker[0].downloads
if item.agent.AnalyticsTracker
and len(item.agent.AnalyticsTracker) > 0
else 0
),
submissionStatus=item.agent.submissionStatus,
)
for item in result.featured_agents
if item.agent is not None
],
)
return ret
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=400, detail=str(e)) from e
except Exception as e:
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {e}"
) from e

View File

@ -1,26 +0,0 @@
import fastapi
import market.db
import market.model
router = fastapi.APIRouter()
@router.post("/agent-installed")
async def agent_installed_endpoint(
event_data: market.model.AgentInstalledFromMarketplaceEventData,
):
"""
Endpoint to track agent installation events from the marketplace.
Args:
event_data (market.model.AgentInstalledFromMarketplaceEventData): The event data.
"""
try:
await market.db.create_agent_installed_event(event_data)
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(
status_code=500, detail=f"An unexpected error occurred: {e}"
)

View File

@ -1,56 +0,0 @@
import typing
import fastapi
import prisma.enums
import market.db
import market.model
import market.utils.extension_types
router = fastapi.APIRouter()
@router.get("/search")
async def search(
query: str,
page: int = fastapi.Query(1, description="The pagination page to start on"),
page_size: int = fastapi.Query(
10, description="The number of items to return per page"
),
categories: typing.List[str] = fastapi.Query(
None, description="The categories to filter by"
),
description_threshold: int = fastapi.Query(
60, description="The number of characters to return from the description"
),
sort_by: str = fastapi.Query("rank", description="Sorting by column"),
sort_order: typing.Literal["desc", "asc"] = fastapi.Query(
"desc", description="The sort order based on sort_by"
),
submission_status: prisma.enums.SubmissionStatus = fastapi.Query(
prisma.enums.SubmissionStatus.APPROVED,
description="The submission status to filter by",
),
) -> market.model.ListResponse[market.utils.extension_types.AgentsWithRank]:
"""searches endpoint for agents
Args:
query (str): the search query
page (int, optional): the pagination page to start on. Defaults to 1.
page_size (int, optional): the number of items to return per page. Defaults to 10.
category (str | None, optional): the agent category to filter by. None is no filter. Defaults to None.
description_threshold (int, optional): the number of characters to return from the description. Defaults to 60.
sort_by (str, optional): Sorting by column. Defaults to "rank".
sort_order ('asc' | 'desc', optional): the sort order based on sort_by. Defaults to "desc".
"""
agents = await market.db.search_db(
query=query,
page=page,
page_size=page_size,
categories=categories,
description_threshold=description_threshold,
sort_by=sort_by,
sort_order=sort_order,
submission_status=submission_status,
)
return agents

View File

@ -1,35 +0,0 @@
import autogpt_libs.auth
import fastapi
import fastapi.responses
import prisma
import market.db
import market.model
import market.utils.analytics
router = fastapi.APIRouter()
@router.post("/agents/submit", response_model=market.model.AgentResponse)
async def submit_agent(
request: market.model.AddAgentRequest,
user: autogpt_libs.auth.User = fastapi.Depends(autogpt_libs.auth.requires_user),
):
"""
A basic endpoint to create a new agent entry in the database.
"""
try:
agent = await market.db.create_agent_entry(
request.graph["name"],
request.graph["description"],
request.author,
request.keywords,
request.categories,
prisma.Json(request.graph),
)
return fastapi.responses.PlainTextResponse(agent.model_dump_json())
except market.db.AgentQueryError as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))
except Exception as e:
raise fastapi.HTTPException(status_code=500, detail=str(e))

View File

@ -1,47 +0,0 @@
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)}")

View File

@ -1,5 +0,0 @@
import prisma.models
class AgentsWithRank(prisma.models.Agents):
rank: float

View File

@ -1,6 +0,0 @@
import prisma.models
prisma.models.Agents.create_partial(
"AgentOnlyDescriptionNameAuthorIdCategories",
include={"name", "author", "id", "categories"},
)

View File

@ -1,61 +0,0 @@
-- 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 ();

View File

@ -1,11 +0,0 @@
/*
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");

View File

@ -1,20 +0,0 @@
-- 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;

View File

@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "FeaturedAgent" ALTER COLUMN "is_featured" SET DEFAULT false;

View File

@ -1,8 +0,0 @@
-- CreateEnum
CREATE TYPE "SubmissionStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
-- AlterTable
ALTER TABLE "Agents" ADD COLUMN "submissionDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "submissionReviewComments" TEXT,
ADD COLUMN "submissionReviewDate" TIMESTAMP(3),
ADD COLUMN "submissionStatus" "SubmissionStatus" NOT NULL DEFAULT 'PENDING';

View File

@ -1,12 +0,0 @@
/*
Warnings:
- You are about to drop the column `category` on the `FeaturedAgent` table. All the data in the column will be lost.
- You are about to drop the column `is_featured` on the `FeaturedAgent` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "FeaturedAgent" DROP COLUMN "category",
DROP COLUMN "is_featured",
ADD COLUMN "featuredCategories" TEXT[],
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT false;

View File

@ -1,19 +0,0 @@
-- CreateEnum
CREATE TYPE "InstallationLocation" AS ENUM ('LOCAL', 'CLOUD');
-- CreateTable
CREATE TABLE "InstallTracker" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"marketplaceAgentId" UUID NOT NULL,
"installedAgentId" UUID NOT NULL,
"installationLocation" "InstallationLocation" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "InstallTracker_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "InstallTracker_marketplaceAgentId_installedAgentId_key" ON "InstallTracker"("marketplaceAgentId", "installedAgentId");
-- AddForeignKey
ALTER TABLE "InstallTracker" ADD CONSTRAINT "InstallTracker_marketplaceAgentId_fkey" FOREIGN KEY ("marketplaceAgentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -1,20 +0,0 @@
-- DropForeignKey
ALTER TABLE "AnalyticsTracker" DROP CONSTRAINT "AnalyticsTracker_agentId_fkey";
-- DropForeignKey
ALTER TABLE "FeaturedAgent" DROP CONSTRAINT "FeaturedAgent_agentId_fkey";
-- DropForeignKey
ALTER TABLE "InstallTracker" DROP CONSTRAINT "InstallTracker_marketplaceAgentId_fkey";
-- DropIndex
DROP INDEX "AnalyticsTracker_agentId_key";
-- AddForeignKey
ALTER TABLE "AnalyticsTracker" ADD CONSTRAINT "AnalyticsTracker_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InstallTracker" ADD CONSTRAINT "InstallTracker_marketplaceAgentId_fkey" FOREIGN KEY ("marketplaceAgentId") REFERENCES "Agents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FeaturedAgent" ADD CONSTRAINT "FeaturedAgent_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,8 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[agentId]` on the table `AnalyticsTracker` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "AnalyticsTracker_agentId_key" ON "AnalyticsTracker"("agentId");

View File

@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
[tool.poetry]
name = "market"
version = "0.1.0"
description = ""
authors = [
"SwiftyOS <craigswift13@gmail.com>",
"Nicholas Tindle <spam@ntindle.com>",
]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
prisma = "^0.15.0"
python-dotenv = "^1.0.1"
uvicorn = "^0.34.0"
fastapi = "^0.115.6"
sentry-sdk = { extras = ["fastapi"], version = "^2.19.2" }
fuzzywuzzy = "^0.18.0"
python-levenshtein = "^0.26.1"
# autogpt-platform-backend = { path = "../backend", develop = true }
prometheus-fastapi-instrumentator = "^7.0.0"
autogpt-libs = {path = "../autogpt_libs"}
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.4"
pytest-asyncio = "^0.25.1"
pytest-watcher = "^0.4.3"
requests = "^2.32.3"
ruff = "^0.8.4"
pyright = "^1.1.391"
isort = "^5.13.2"
black = "^24.10.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
format = "scripts:format"
lint = "scripts:lint"
app = "scripts:app"
setup = "scripts:setup"
populate = "scripts:populate_database"
[tool.pytest-watcher]
now = false
clear = true
delay = 0.2
runner = "pytest"
runner_args = []
patterns = ["*.py"]
ignore_patterns = []
[tool.pytest.ini_options]
asyncio_mode = "auto"

View File

@ -1,80 +0,0 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-py"
recursive_type_depth = 5
interface = "asyncio"
previewFeatures = ["fullTextSearch"]
partial_type_generator = "market/utils/partial_types.py"
}
enum SubmissionStatus {
PENDING
APPROVED
REJECTED
}
model Agents {
id String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
version Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
submissionDate DateTime @default(now())
submissionReviewDate DateTime?
submissionStatus SubmissionStatus @default(PENDING)
submissionReviewComments String?
name String?
description String?
author String?
keywords String[]
categories String[]
search Unsupported("tsvector")? @default(dbgenerated("''::tsvector"))
graph Json
AnalyticsTracker AnalyticsTracker[]
FeaturedAgent FeaturedAgent?
InstallTracker InstallTracker[]
@@id(name: "graphVersionId", [id, version])
}
model AnalyticsTracker {
id String @id @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
agentId String @unique @db.Uuid
agent Agents @relation(fields: [agentId], references: [id], onDelete: Cascade)
views Int
downloads Int
}
enum InstallationLocation {
LOCAL
CLOUD
}
model InstallTracker {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
marketplaceAgentId String @db.Uuid
marketplaceAgent Agents @relation(fields: [marketplaceAgentId], references: [id], onDelete: Cascade)
installedAgentId String @db.Uuid
installationLocation InstallationLocation
createdAt DateTime @default(now())
@@unique([marketplaceAgentId, installedAgentId])
}
model FeaturedAgent {
id String @id @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid
agentId String @unique @db.Uuid
agent Agents @relation(fields: [agentId], references: [id], onDelete: Cascade)
isActive Boolean @default(false)
featuredCategories String[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@ -1,65 +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 populate_database():
import glob
import json
import pathlib
import requests
import market.model
templates = pathlib.Path(__file__).parent.parent / "graph_templates"
all_files = glob.glob(str(templates / "*.json"))
for file in all_files:
with open(file, "r") as f:
data = f.read()
req = market.model.AddAgentRequest(
graph=json.loads(data),
author="Populate DB",
categories=["Pre-Populated"],
keywords=["test"],
)
response = requests.post(
"http://localhost:8015/api/v1/market/admin/agent", json=req.model_dump()
)
print(response.text)
def format():
run("ruff", "check", "--fix", ".")
run("isort", "--profile", "black", ".")
run("black", ".")
run("pyright", ".")
def app():
port = os.getenv("PORT", "8015")
run("uvicorn", "market.app:app", "--reload", "--port", port, "--host", "0.0.0.0")
def setup():
run("prisma", "generate")
run("prisma", "migrate", "deploy")

View File

@ -1,79 +0,0 @@
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

View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
[[package]]
name = "agent-protocol-client"
@ -6,6 +6,8 @@ version = "1.1.0"
description = "Agent Communication Protocol Client"
optional = false
python-versions = "^3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = []
develop = false
@ -28,6 +30,8 @@ version = "3.9.3"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"},
{file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"},
@ -124,6 +128,8 @@ version = "1.3.1"
description = "aiosignal: a list of registered asynchronous callbacks"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
@ -138,6 +144,8 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@ -149,6 +157,8 @@ version = "4.2.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"},
{file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"},
@ -171,6 +181,8 @@ version = "2.4.1"
description = "Annotate AST trees with source code positions"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
@ -189,6 +201,8 @@ version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version < \"3.11\""
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
@ -200,6 +214,8 @@ version = "23.2.0"
description = "Classes Without Boilerplate"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
@ -219,6 +235,8 @@ version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
{file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
@ -265,6 +283,8 @@ version = "5.3.2"
description = "Extensible memoizing collections and decorators"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"},
{file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"},
@ -276,6 +296,8 @@ version = "2023.11.17"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
@ -287,6 +309,8 @@ version = "1.16.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "os_name == \"nt\" and implementation_name != \"pypy\" and (python_version <= \"3.11\" or python_version >= \"3.12\")"
files = [
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
@ -351,6 +375,8 @@ version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
@ -362,6 +388,8 @@ version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
@ -461,6 +489,8 @@ version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
@ -475,6 +505,8 @@ version = "1.2.4"
description = "click_default_group"
optional = false
python-versions = ">=2.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"},
{file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"},
@ -492,6 +524,8 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@ -503,6 +537,8 @@ version = "1.2.0"
description = "Python library for calculating contours of 2D quadrilateral grids"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"},
{file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"},
@ -566,6 +602,8 @@ version = "7.5.1"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"},
{file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"},
@ -633,6 +671,8 @@ version = "0.12.1"
description = "Composable style cycles"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
{file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
@ -648,6 +688,8 @@ version = "5.1.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.5"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
@ -659,6 +701,8 @@ version = "0.3.8"
description = "Distribution utilities"
optional = false
python-versions = "*"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
@ -670,6 +714,8 @@ version = "1.9.0"
description = "Distro - an OS platform information API"
optional = false
python-versions = ">=3.6"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"},
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
@ -681,6 +727,8 @@ version = "1.2.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
@ -695,6 +743,8 @@ version = "2.0.1"
description = "Get the currently executing AST node of a frame, and other information"
optional = false
python-versions = ">=3.5"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
{file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
@ -709,6 +759,8 @@ version = "0.109.2"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "fastapi-0.109.2-py3-none-any.whl", hash = "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d"},
{file = "fastapi-0.109.2.tar.gz", hash = "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73"},
@ -728,6 +780,8 @@ version = "3.13.1"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
@ -744,6 +798,8 @@ version = "7.0.0"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.8.1"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
{file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
@ -760,6 +816,8 @@ version = "4.47.2"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"},
{file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"},
@ -825,6 +883,8 @@ version = "1.4.1"
description = "A list-like structure which implements collections.abc.MutableSequence"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"},
{file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"},
@ -911,6 +971,8 @@ version = "4.0.11"
description = "Git Object Database"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
{file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
@ -925,6 +987,8 @@ version = "3.1.41"
description = "GitPython is a Python library used to interact with Git repositories"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"},
{file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"},
@ -942,6 +1006,8 @@ version = "2.26.2"
description = "Google Authentication Library"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "google-auth-2.26.2.tar.gz", hash = "sha256:97327dbbf58cccb58fc5a1712bba403ae76668e64814eb30f7316f7e27126b81"},
{file = "google_auth-2.26.2-py2.py3-none-any.whl", hash = "sha256:3f445c8ce9b61ed6459aad86d8ccdba4a9afed841b2d1451a11ef4db08957424"},
@ -965,6 +1031,8 @@ version = "1.2.0"
description = "Google Authentication Library"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "google-auth-oauthlib-1.2.0.tar.gz", hash = "sha256:292d2d3783349f2b0734a0a0207b1e1e322ac193c2c09d8f7c613fb7cc501ea8"},
{file = "google_auth_oauthlib-1.2.0-py2.py3-none-any.whl", hash = "sha256:297c1ce4cb13a99b5834c74a1fe03252e1e499716718b190f56bcb9c4abc4faf"},
@ -983,6 +1051,8 @@ version = "5.12.4"
description = "Google Spreadsheets Python API"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "gspread-5.12.4-py3-none-any.whl", hash = "sha256:1e453d87e0fde23bc5546e33eb684cf8b8c26540615f2f1ae004a9084a29051d"},
{file = "gspread-5.12.4.tar.gz", hash = "sha256:3fcef90183f15d3c9233b4caa021a83682f2b2ee678340c42d7ca7d8be98c6d1"},
@ -998,6 +1068,8 @@ version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@ -1009,6 +1081,8 @@ version = "0.17.3"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"},
{file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"},
@ -1030,6 +1104,8 @@ version = "0.22.0"
description = "A comprehensive HTTP client library."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"},
{file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"},
@ -1044,6 +1120,8 @@ version = "0.24.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"},
{file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"},
@ -1067,6 +1145,8 @@ version = "2.5.33"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"},
{file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"},
@ -1081,6 +1161,8 @@ version = "3.6"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
@ -1092,6 +1174,8 @@ version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@ -1103,6 +1187,8 @@ version = "8.20.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.10"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"},
{file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"},
@ -1139,6 +1225,8 @@ version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
@ -1153,6 +1241,8 @@ version = "0.19.1"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
{file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
@ -1172,6 +1262,8 @@ version = "3.1.3"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
@ -1189,6 +1281,8 @@ version = "3.0.2"
description = "Python library for serializing any arbitrary object graph into JSON"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "jsonpickle-3.0.2-py3-none-any.whl", hash = "sha256:4a8442d97ca3f77978afa58068768dba7bff2dbabe79a9647bc3cdafd4ef019f"},
{file = "jsonpickle-3.0.2.tar.gz", hash = "sha256:e37abba4bfb3ca4a4647d28bb9f4706436f7b46c8a8333b4a718abafa8e46b37"},
@ -1205,6 +1299,8 @@ version = "1.4.5"
description = "A fast implementation of the Cassowary constraint solver"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"},
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"},
@ -1318,6 +1414,8 @@ version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
@ -1387,6 +1485,8 @@ version = "3.8.2"
description = "Python plotting package"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"},
{file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"},
@ -1435,6 +1535,8 @@ version = "0.1.6"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.5"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
{file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
@ -1449,6 +1551,8 @@ version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
@ -1460,6 +1564,8 @@ version = "6.0.5"
description = "multidict implementation"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"},
{file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"},
@ -1559,6 +1665,8 @@ version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@ -1570,6 +1678,8 @@ version = "3.2.1"
description = "Python package for creating and manipulating graphs and networks"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"},
{file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"},
@ -1588,6 +1698,8 @@ version = "1.8.0"
description = "Node.js virtual environment builder"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
@ -1602,6 +1714,8 @@ version = "1.26.3"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"},
{file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"},
@ -1647,6 +1761,8 @@ version = "4.1.3"
description = "OAuth 2.0 client library"
optional = false
python-versions = "*"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "oauth2client-4.1.3-py2.py3-none-any.whl", hash = "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac"},
{file = "oauth2client-4.1.3.tar.gz", hash = "sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6"},
@ -1665,6 +1781,8 @@ version = "3.2.2"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
{file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
@ -1681,6 +1799,8 @@ version = "1.7.2"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.7.1"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "openai-1.7.2-py3-none-any.whl", hash = "sha256:8f41b90a762f5fd9d182b45851041386fed94c8ad240a70abefee61a68e0ef53"},
{file = "openai-1.7.2.tar.gz", hash = "sha256:c73c78878258b07f1b468b0602c6591f25a1478f49ecb90b9bd44b7cc80bce73"},
@ -1704,6 +1824,8 @@ version = "1.3.0.post0"
description = "Capture the outcome of Python function calls."
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
{file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
@ -1718,6 +1840,8 @@ version = "23.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
@ -1729,6 +1853,8 @@ version = "2.1.4"
description = "Powerful data structures for data analysis, time series, and statistics"
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pandas-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdec823dc6ec53f7a6339a0e34c68b144a7a1fd28d80c260534c39c62c5bf8c9"},
{file = "pandas-2.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:294d96cfaf28d688f30c918a765ea2ae2e0e71d3536754f4b6de0ea4a496d034"},
@ -1797,6 +1923,8 @@ version = "0.8.3"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
@ -1812,6 +1940,8 @@ version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
@ -1823,6 +1953,8 @@ version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications."
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
@ -1837,6 +1969,8 @@ version = "10.2.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
{file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"},
@ -1922,6 +2056,8 @@ version = "4.1.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
{file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
@ -1937,6 +2073,8 @@ version = "1.3.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
@ -1952,6 +2090,8 @@ version = "3.6.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"},
{file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"},
@ -1970,6 +2110,8 @@ version = "3.0.43"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.7.0"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
{file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
@ -1984,6 +2126,8 @@ version = "5.9.7"
description = "Cross-platform lib for process and system monitoring in Python."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"},
{file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"},
@ -2012,6 +2156,8 @@ version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
@ -2023,6 +2169,8 @@ version = "0.2.2"
description = "Safely evaluate AST nodes without side effects"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
@ -2037,6 +2185,8 @@ version = "0.5.1"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"},
{file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"},
@ -2048,6 +2198,8 @@ version = "0.3.0"
description = "A collection of ASN.1-based protocols modules"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"},
{file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"},
@ -2062,6 +2214,8 @@ version = "2.11.1"
description = "Python style guide checker"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
{file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
@ -2073,6 +2227,8 @@ version = "2.21"
description = "C parser in Python"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
markers = "os_name == \"nt\" and implementation_name != \"pypy\" and (python_version <= \"3.11\" or python_version >= \"3.12\")"
files = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
@ -2084,6 +2240,8 @@ version = "2.7.4"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"},
{file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"},
@ -2103,6 +2261,8 @@ version = "2.18.4"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"},
{file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"},
@ -2194,6 +2354,8 @@ version = "2.3.4"
description = "Settings management using Pydantic"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pydantic_settings-2.3.4-py3-none-any.whl", hash = "sha256:11ad8bacb68a045f00e4f862c7a718c8a9ec766aa8fd4c32e39a0594b207b53a"},
{file = "pydantic_settings-2.3.4.tar.gz", hash = "sha256:c5802e3d62b78e82522319bbc9b8f8ffb28ad1c988a99311d04f2a6051fca0a7"},
@ -2213,6 +2375,8 @@ version = "3.2.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
@ -2224,6 +2388,8 @@ version = "2.17.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
@ -2239,6 +2405,8 @@ version = "3.1.1"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false
python-versions = ">=3.6.8"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"},
{file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"},
@ -2253,6 +2421,8 @@ version = "1.1.366"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyright-1.1.366-py3-none-any.whl", hash = "sha256:c09e73ccc894976bcd6d6a5784aa84d724dbd9ceb7b873b39d475ca61c2de071"},
{file = "pyright-1.1.366.tar.gz", hash = "sha256:10e4d60be411f6d960cd39b0b58bf2ff76f2c83b9aeb102ffa9d9fda2e1303cb"},
@ -2271,6 +2441,8 @@ version = "1.7.1"
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
@ -2283,6 +2455,8 @@ version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
@ -2305,6 +2479,8 @@ version = "0.23.7"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
{file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
@ -2323,6 +2499,8 @@ version = "5.0.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
@ -2341,6 +2519,8 @@ version = "2.8.2"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
@ -2355,6 +2535,8 @@ version = "1.0.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
{file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
@ -2369,6 +2551,8 @@ version = "0.0.7"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "python_multipart-0.0.7-py3-none-any.whl", hash = "sha256:b1fef9a53b74c795e2347daac8c54b252d9e0df9c619712691c1cc8021bd3c49"},
{file = "python_multipart-0.0.7.tar.gz", hash = "sha256:288a6c39b06596c1b988bb6794c6fbc80e6c369e35e5062637df256bee0c9af9"},
@ -2383,6 +2567,8 @@ version = "2023.3.post1"
description = "World timezone definitions, modern and historical"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
{file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
@ -2394,6 +2580,8 @@ version = "0.3.2"
description = "A Python network graph visualization library"
optional = false
python-versions = ">3.6"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "pyvis-0.3.2-py3-none-any.whl", hash = "sha256:5720c4ca8161dc5d9ab352015723abb7a8bb8fb443edeb07f7a322db34a97555"},
]
@ -2410,6 +2598,8 @@ version = "6.0.1"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
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"},
@ -2470,6 +2660,8 @@ version = "2.31.0"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
@ -2491,6 +2683,8 @@ version = "1.3.1"
description = "OAuthlib authentication support for Requests."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
@ -2509,6 +2703,8 @@ version = "4.9"
description = "Pure-Python RSA implementation"
optional = false
python-versions = ">=3.6,<4"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
@ -2523,6 +2719,8 @@ version = "4.16.0"
description = ""
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "selenium-4.16.0-py3-none-any.whl", hash = "sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f"},
{file = "selenium-4.16.0.tar.gz", hash = "sha256:b2e987a445306151f7be0e6dfe2aa72a479c2ac6a91b9d5ef2d6dd4e49ad0435"},
@ -2540,6 +2738,8 @@ version = "69.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
{file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
@ -2556,6 +2756,8 @@ version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@ -2567,6 +2769,8 @@ version = "5.0.1"
description = "A pure Python implementation of a sliding window memory map manager"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
{file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
@ -2578,6 +2782,8 @@ version = "1.3.0"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
@ -2589,6 +2795,8 @@ version = "2.4.0"
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
@ -2600,6 +2808,8 @@ version = "0.6.3"
description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
@ -2619,6 +2829,8 @@ version = "0.36.3"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"},
{file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"},
@ -2636,6 +2848,8 @@ version = "0.9.0"
description = "Pretty-print tabular data"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
{file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
@ -2650,6 +2864,8 @@ version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
@ -2661,6 +2877,8 @@ version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version < \"3.11\""
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
@ -2672,6 +2890,8 @@ version = "4.66.1"
description = "Fast, Extensible Progress Meter"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"},
{file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"},
@ -2692,6 +2912,8 @@ version = "5.14.1"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
{file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
@ -2707,6 +2929,8 @@ version = "0.24.0"
description = "A friendly Python library for async concurrency and I/O"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "trio-0.24.0-py3-none-any.whl", hash = "sha256:c3bd3a4e3e3025cd9a2241eae75637c43fe0b9e88b4c97b9161a55b9e54cd72c"},
{file = "trio-0.24.0.tar.gz", hash = "sha256:ffa09a74a6bf81b84f8613909fb0beaee84757450183a7a2e0b47b455c0cac5d"},
@ -2727,6 +2951,8 @@ version = "0.11.1"
description = "WebSocket library for Trio"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"},
{file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"},
@ -2743,10 +2969,12 @@ version = "4.9.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
]
markers = {main = "python_version <= \"3.11\" or python_version >= \"3.12\"", dev = "python_version < \"3.11\""}
[[package]]
name = "tzdata"
@ -2754,6 +2982,8 @@ version = "2023.4"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"},
{file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"},
@ -2765,6 +2995,8 @@ version = "1.26.18"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
groups = ["main", "dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
{file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
@ -2784,6 +3016,8 @@ version = "0.23.2"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"},
{file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"},
@ -2803,6 +3037,8 @@ version = "20.25.0"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
{file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
@ -2823,6 +3059,8 @@ version = "0.2.13"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
@ -2834,6 +3072,8 @@ version = "1.2.0"
description = "WebSockets state-machine based protocol implementation"
optional = false
python-versions = ">=3.7.0"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
{file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
@ -2848,6 +3088,8 @@ version = "1.9.4"
description = "Yet another URL library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
files = [
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"},
{file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"},
@ -2946,6 +3188,6 @@ idna = ">=2.0"
multidict = ">=4.0"
[metadata]
lock-version = "2.0"
lock-version = "2.1"
python-versions = "^3.10"
content-hash = "26bd75befe5223095b65be293086edf52f34f9043e49107c80a105dc0387dd6a"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff