Merge branch 'dev' into kpczerwinski/secrt-1012-mvp-implement-top-up-flow
This commit is contained in:
commit
38b662b107
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 }}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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]
|
||||
|
|
|
@ -216,6 +216,7 @@ enum AgentExecutionStatus {
|
|||
QUEUED
|
||||
RUNNING
|
||||
COMPLETED
|
||||
TERMINATED
|
||||
FAILED
|
||||
}
|
||||
|
||||
|
@ -638,4 +639,4 @@ enum APIKeyStatus {
|
|||
ACTIVE
|
||||
REVOKED
|
||||
SUSPENDED
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: ../
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -1,6 +0,0 @@
|
|||
database.db
|
||||
database.db-journal
|
||||
build/
|
||||
config.json
|
||||
secrets/*
|
||||
!secrets/.gitkeep
|
|
@ -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"]
|
|
@ -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.
|
|
@ -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"
|
|
@ -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)
|
|
@ -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},
|
||||
}
|
|
@ -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)}")
|
|
@ -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
|
|
@ -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))
|
|
@ -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
|
|
@ -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
|
|
@ -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}"
|
||||
)
|
|
@ -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
|
|
@ -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))
|
|
@ -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)}")
|
|
@ -1,5 +0,0 @@
|
|||
import prisma.models
|
||||
|
||||
|
||||
class AgentsWithRank(prisma.models.Agents):
|
||||
rank: float
|
|
@ -1,6 +0,0 @@
|
|||
import prisma.models
|
||||
|
||||
prisma.models.Agents.create_partial(
|
||||
"AgentOnlyDescriptionNameAuthorIdCategories",
|
||||
include={"name", "author", "id", "categories"},
|
||||
)
|
|
@ -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 ();
|
|
@ -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");
|
|
@ -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;
|
|
@ -1,2 +0,0 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "FeaturedAgent" ALTER COLUMN "is_featured" SET DEFAULT false;
|
|
@ -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';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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");
|
|
@ -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
|
@ -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"
|
|
@ -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
|
||||
}
|
|
@ -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")
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue