chore: release 0.11.6 2 (#2779)

This commit is contained in:
Kian Jones
2025-08-26 22:07:33 -07:00
committed by GitHub
37 changed files with 675 additions and 10192 deletions

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"] # Adjust Python version matrix if needed
python-version: ["3.11"] # Removed 3.12+ as minimal sets the standard. Adjust Python version matrix if needed
steps:
- name: Checkout code
@@ -20,12 +20,21 @@ jobs:
ref: ${{ github.head_ref }} # Checkout the PR branch
fetch-depth: 0 # Fetch all history for all branches and tags
- name: "Setup Python, Poetry and Dependencies"
uses: packetcoders/action-setup-cache-python-poetry@main
- name: Set up python
id: setup-python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
poetry-version: "2.1.3"
install-args: "-E dev -E postgres -E external-tools -E tests" # Adjust as necessary
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
activate-environment: true
- name: Install Dependencies
run: |
uv sync --extra dev --extra postgres --extra external-tools
- name: Validate PR Title
if: github.event_name == 'pull_request'
@@ -41,10 +50,10 @@ jobs:
continue-on-error: true
- name: Run isort
run: poetry run isort --profile black --check-only --diff .
run: uv run isort --profile black --check-only --diff .
- name: Run Black
run: poetry run black --check .
run: uv run black --check .
- name: Run Autoflake
run: poetry run autoflake --remove-all-unused-imports --remove-unused-variables --in-place --recursive --ignore-init-module-imports .
run: uv run autoflake --remove-all-unused-imports --remove-unused-variables --in-place --recursive --ignore-init-module-imports .

View File

@@ -13,10 +13,17 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Python
- name: Set up python 3.11
id: setup-python
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: 3.11
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Set permissions for log directory
run: |
@@ -34,12 +41,6 @@ jobs:
run: |
docker compose -f dev-compose.yaml up --build -d
#- name: "Setup Python, Poetry and Dependencies"
# uses: packetcoders/action-setup-cache-python-poetry@v1.2.0
# with:
# python-version: "3.11"
# poetry-version: "1.8.2"
# install-args: "--all-extras"
- name: Wait for service
run: bash scripts/wait_for_service.sh http://localhost:8283 -- echo "Service is ready"
@@ -55,9 +56,8 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PYTHONPATH: ${{ github.workspace }}:${{ env.PYTHONPATH }}
run: |
pipx install poetry==2.1.3
poetry install -E dev -E postgres
poetry run pytest -s tests/test_client.py
uv sync --extra dev --extra postgres --extra sqlite
uv run pytest -s tests/test_client.py
- name: Print docker logs if tests fail
if: failure()

View File

@@ -26,12 +26,22 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- run: psql -h localhost -U postgres -d postgres -c 'CREATE EXTENSION vector'
- name: "Setup Python, Poetry and Dependencies"
uses: packetcoders/action-setup-cache-python-poetry@main
- name: Set up python 3.11
id: setup-python
uses: actions/setup-python@v5
with:
python-version: "3.11"
poetry-version: "1.8.2"
install-args: "--all-extras"
python-version: 3.11
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Install Dependencies
run: |
uv sync --all-extras
- name: Test alembic migration
env:
LETTA_PG_PORT: 5432
@@ -40,5 +50,5 @@ jobs:
LETTA_PG_DB: postgres
LETTA_PG_HOST: localhost
run: |
poetry run alembic upgrade head
poetry run alembic check
uv run alembic upgrade head
uv run alembic check

View File

@@ -61,7 +61,7 @@ jobs:
- name: Install dependencies
shell: bash
run: poetry install --no-interaction --no-root ${{ inputs.install-args || '-E dev -E postgres -E external-tools -E tests -E cloud-tool-sandbox -E google' }}
run: uv sync --extra dev --extra postgres --extra external-tools --extra cloud-tool-sandbox --extra google
- name: Migrate database
env:
LETTA_PG_PORT: 5432
@@ -71,7 +71,7 @@ jobs:
LETTA_PG_HOST: localhost
run: |
psql -h localhost -U postgres -d postgres -c 'CREATE EXTENSION vector'
poetry run alembic upgrade head
uv run alembic upgrade head
- name: Run integration tests
# if any of the 1000+ test cases fail, pytest reports exit code 1 and won't procces/upload the results
@@ -94,7 +94,7 @@ jobs:
DEEPSEEK_API_KEY: ${{ env.DEEPSEEK_API_KEY}}
LETTA_USE_EXPERIMENTAL: 1
run: |
poetry run pytest \
uv run pytest \
-s -vv \
.github/scripts/model-sweep/model_sweep.py \
--json-report --json-report-file=.github/scripts/model-sweep/model_sweep_report.json --json-report-indent=4
@@ -103,7 +103,7 @@ jobs:
continue-on-error: true
# file path args to generate_model_sweep_markdown.py are relative to the script
run: |
poetry run python \
uv run python \
.github/scripts/model-sweep/generate_model_sweep_markdown.py \
.github/scripts/model-sweep/model_sweep_report.json \
.github/scripts/model-sweep/supported-models.mdx

View File

@@ -1,4 +1,4 @@
name: poetry-publish-nightly
name: uv-publish-nightly
on:
schedule:
- cron: '35 10 * * *' # 10:35am UTC, 2:35am PST, 5:35am EST
@@ -31,11 +31,17 @@ jobs:
- name: Check out the repository
uses: actions/checkout@v4
- name: "Setup Python, Poetry and Dependencies"
uses: packetcoders/action-setup-cache-python-poetry@main
- name: Set up python 3.12
id: setup-python
uses: actions/setup-python@v5
with:
python-version: "3.11"
poetry-version: "1.7.1"
python-version: 3.12
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
activate-environment: true
- name: Set release version
run: |
@@ -50,13 +56,10 @@ jobs:
cat pyproject.toml
cat letta/__init__.py
- name: Configure poetry
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN}}
run: poetry config pypi-token.pypi "$PYPI_TOKEN"
- name: Build the Python package
run: poetry build
run: uv build
- name: Publish the package to PyPI
run: poetry publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: uv publish

View File

@@ -1,4 +1,4 @@
name: poetry-publish
name: uv-publish
on:
release:
types: [published]
@@ -13,20 +13,23 @@ jobs:
- name: Check out the repository
uses: actions/checkout@v4
- name: "Setup Python, Poetry and Dependencies"
uses: packetcoders/action-setup-cache-python-poetry@main
- name: Set up python 3.12
id: setup-python
uses: actions/setup-python@v5
with:
python-version: "3.11"
poetry-version: "1.7.1"
python-version: 3.12
- name: Configure poetry
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
poetry config pypi-token.pypi "$PYPI_TOKEN"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
activate-environment: true
cache-dependency-glob: "uv.lock"
- name: Build the Python package
run: poetry build
run: uv build
- name: Publish the package to PyPI
run: poetry publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: uv publish

View File

@@ -100,35 +100,25 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Load cached Poetry Binary
id: cached-poetry-binary
uses: actions/cache@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
path: ~/.local
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-1.8.3
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.8.3
virtualenvs-create: true
virtualenvs-in-project: true
version: "latest"
- name: Load cached venv
id: cached-poetry-dependencies
id: cached-uv-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}${{ inputs.install-args || '-E dev -E postgres -E external-tools -E tests -E cloud-tool-sandbox' }}
# Restore cache with this prefix if not exact match with key
# Note cache-hit returns false in this case, so the below step will run
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/uv.lock') }}
restore-keys: |
venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
if: steps.cached-uv-dependencies.outputs.cache-hit != 'true'
shell: bash
run: poetry install --no-interaction --no-root ${{ inputs.install-args || '-E dev -E postgres -E external-tools -E tests -E cloud-tool-sandbox -E google' }}
- name: Install letta packages via Poetry
run: uv sync --extra dev --extra postgres --extra external-tools --extra cloud-tool-sandbox --extra google
- name: Install letta packages
run: |
poetry run pip install --upgrade letta-client letta
uv run pip install --upgrade letta-client letta
- name: Migrate database
env:
LETTA_PG_PORT: 5432
@@ -138,7 +128,7 @@ jobs:
LETTA_PG_HOST: localhost
run: |
psql -h localhost -U postgres -d postgres -c 'CREATE EXTENSION vector'
poetry run alembic upgrade head
uv run alembic upgrade head
- name: Run integration tests for ${{ matrix.config_file }}
env:
LLM_CONFIG_FILE: ${{ matrix.config_file }}
@@ -161,7 +151,7 @@ jobs:
GOOGLE_CLOUD_LOCATION: ${{ secrets.GOOGLE_CLOUD_LOCATION }}
LETTA_GEMINI_FORCE_MINIMUM_THINKING_BUDGET: true
run: |
poetry run pytest \
uv run pytest \
-s -vv \
tests/integration_test_send_message.py \
--maxfail=1 --durations=10

View File

@@ -1,13 +1,13 @@
name: Check Poetry Dependencies Changes
name: Check uv Dependencies Changes
on:
pull_request:
paths:
- 'poetry.lock'
- 'uv.lock'
- 'pyproject.toml'
jobs:
check-poetry-changes:
check-uv-changes:
runs-on: ubuntu-latest
permissions:
pull-requests: write
@@ -17,13 +17,13 @@ jobs:
with:
fetch-depth: 0
- name: Check for poetry.lock changes
id: check-poetry-lock
- name: Check for uv.lock changes
id: check-uv-lock
run: |
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -q "poetry.lock"; then
echo "poetry_lock_changed=true" >> $GITHUB_OUTPUT
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep -q "uv.lock"; then
echo "uv_lock_changed=true" >> $GITHUB_OUTPUT
else
echo "poetry_lock_changed=false" >> $GITHUB_OUTPUT
echo "uv_lock_changed=false" >> $GITHUB_OUTPUT
fi
- name: Check for pyproject.toml changes
@@ -36,19 +36,19 @@ jobs:
fi
- name: Create PR comment
if: steps.check-poetry-lock.outputs.poetry_lock_changed == 'true' || steps.check-pyproject.outputs.pyproject_changed == 'true'
if: steps.check-uv-lock.outputs.uv_lock_changed == 'true' || steps.check-pyproject.outputs.pyproject_changed == 'true'
uses: actions/github-script@v7
with:
script: |
const poetryLockChanged = ${{ steps.check-poetry-lock.outputs.poetry_lock_changed }};
const uvLockChanged = ${{ steps.check-uv-lock.outputs.uv_lock_changed }};
const pyprojectChanged = ${{ steps.check-pyproject.outputs.pyproject_changed }};
let message = '📦 Dependencies Alert:\n\n';
if (poetryLockChanged && pyprojectChanged) {
message += '- Both `poetry.lock` and `pyproject.toml` have been modified\n';
} else if (poetryLockChanged) {
message += '- `poetry.lock` has been modified\n';
if (uvLockChanged && pyprojectChanged) {
message += '- Both `uv.lock` and `pyproject.toml` have been modified\n';
} else if (uvLockChanged) {
message += '- `uv.lock` has been modified\n';
} else if (pyprojectChanged) {
message += '- `pyproject.toml` has been modified\n';
}

24
.gitignore vendored
View File

@@ -445,31 +445,7 @@ target/
# IPython
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm

View File

@@ -13,18 +13,18 @@ repos:
hooks:
- id: autoflake
name: autoflake
entry: bash -c '[ -d "apps/core" ] && cd apps/core; poetry run autoflake --remove-all-unused-imports --remove-unused-variables --in-place --recursive --ignore-init-module-imports .'
entry: bash -c '[ -d "apps/core" ] && cd apps/core; uv run autoflake --remove-all-unused-imports --remove-unused-variables --in-place --recursive --ignore-init-module-imports .'
language: system
types: [python]
- id: isort
name: isort
entry: bash -c '[ -d "apps/core" ] && cd apps/core; poetry run isort --profile black .'
entry: bash -c '[ -d "apps/core" ] && cd apps/core; uv run isort --profile black .'
language: system
types: [python]
exclude: ^docs/
- id: black
name: black
entry: bash -c '[ -d "apps/core" ] && cd apps/core; poetry run black --line-length 140 --target-version py310 --target-version py311 .'
entry: bash -c '[ -d "apps/core" ] && cd apps/core; uv run black --line-length 140 --target-version py310 --target-version py311 .'
language: system
types: [python]
exclude: ^docs/

View File

@@ -21,20 +21,20 @@ git clone https://github.com/your-username/letta.git
### 🧩 Install dependencies & configure environment
#### Install poetry and dependencies
#### Install uv and dependencies
First, install Poetry using [the official instructions here](https://python-poetry.org/docs/#installation).
First, install uv using [the official instructions here](https://docs.astral.sh/uv/getting-started/installation/).
Once Poetry is installed, navigate to the letta directory and install the Letta project with Poetry:
Once uv is installed, navigate to the letta directory and install the Letta project with uv:
```shell
cd letta
eval $(poetry env activate)
poetry install --all-extras
eval $(uv env activate)
uv sync --all-extras
```
#### Setup PostgreSQL environment (optional)
If you are planning to develop letta connected to PostgreSQL database, you need to take the following actions.
If you are not planning to use PostgreSQL database, you can skip to the step which talks about [running letta](#running-letta-with-poetry).
If you are not planning to use PostgreSQL database, you can skip to the step which talks about [running letta](#running-letta-with-uv).
Assuming you have a running PostgreSQL instance, first you need to create the user, database and ensure the pgvector
extension is ready. Here are sample steps for a case where user and database name is letta and assumes no password is set:
@@ -50,32 +50,25 @@ export LETTA_PG_URI="postgresql://${POSTGRES_USER:-letta}:${POSTGRES_PASSWORD:-l
```
After this you need to prep the database with initial content. You can use alembic upgrade to populate the initial
contents from template test data. Please ensure to activate poetry environment using `poetry shell`.
contents from template test data.
```shell
alembic upgrade head
uv run alembic upgrade head
```
#### Running letta with poetry
Now when you want to use `letta`, make sure you first activate the `poetry` environment using poetry shell:
#### Running letta with uv
Now when you want to use `letta`, you can use `uv run` to run any letta command:
```shell
$ eval $(poetry env activate)
(letta-py3.12) $ letta run
```
Alternatively, you can use `poetry run` (which will activate the `poetry` environment for the `letta run` command only):
```shell
poetry run letta run
uv run letta run
```
#### Installing pre-commit
We recommend installing pre-commit to ensure proper formatting during development:
```
poetry run pre-commit install
poetry run pre-commit run --all-files
uv run pre-commit install
uv run pre-commit run --all-files
```
If you don't install pre-commit, you will need to run `poetry run black .` before submitting a PR.
If you don't install pre-commit, you will need to run `uv run black .` before submitting a PR.
## 2. 🛠️ Making Changes
@@ -95,13 +88,13 @@ Now, the world is your oyster! Go ahead and craft your fabulous changes. 🎨
#### Handling Database Migrations
If you are running Letta for the first time, your database will be automatically be setup. If you are updating Letta, you may need to run migrations. To run migrations, use the following command:
```shell
poetry run alembic upgrade head
uv run alembic upgrade head
```
#### Creating a new Database Migration
If you have made changes to the database models, you will need to create a new migration. To create a new migration, use the following command:
```shell
poetry run alembic revision --autogenerate -m "Your migration message here"
uv run alembic revision --autogenerate -m "Your migration message here"
```
Visit the [Alembic documentation](https://alembic.sqlalchemy.org/en/latest/tutorial.html) for more information on creating and running migrations.
@@ -112,9 +105,9 @@ Before we hit the 'Wow, I'm Done' button, let's make sure everything works as ex
### Run existing tests
Running tests if you installed via poetry:
Running tests:
```
poetry run pytest -s tests
uv run pytest -s tests
```
Running tests if you installed via pip:
@@ -126,14 +119,14 @@ pytest -s tests
If you added a major feature change, please add new tests in the `tests/` directory.
## 4. 🧩 Adding new dependencies
If you need to add a new dependency to Letta, please add the package via `poetry add <PACKAGE_NAME>`. This will update the `pyproject.toml` and `poetry.lock` files. If the dependency does not need to be installed by all users, make sure to mark the dependency as optional in the `pyproject.toml` file and if needed, create a new extra under `[tool.poetry.extras]`.
If you need to add a new dependency to Letta, please add the package via `uv add <PACKAGE_NAME>`. This will update the `pyproject.toml` and `uv.lock` files. If the dependency does not need to be installed by all users, make sure to mark the dependency as optional in the `pyproject.toml` file and if needed, create a new extra under `[project.optional-dependencies]`.
## 5. 🚀 Submitting Changes
### Check Formatting
Please ensure your code is formatted correctly by running:
```
poetry run black . -l 140
uv run black . -l 140
```
### 🚀 Create a Pull Request

View File

@@ -5,7 +5,6 @@ FROM ankane/pgvector:v0.5.1 AS builder
RUN apt-get update && apt-get install -y \
python3 \
python3-venv \
python3-pip \
python3-full \
build-essential \
libpq-dev \
@@ -14,10 +13,9 @@ RUN apt-get update && apt-get install -y \
ARG LETTA_ENVIRONMENT=DEV
ENV LETTA_ENVIRONMENT=${LETTA_ENVIRONMENT} \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache
UV_NO_PROGRESS=1 \
UV_PYTHON_PREFERENCE=system \
UV_CACHE_DIR=/tmp/uv_cache
# Set for other builds
ARG LETTA_VERSION
@@ -29,17 +27,16 @@ WORKDIR /app
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Now install poetry in the virtual environment
RUN pip install --no-cache-dir poetry==2.1.3
# Now install uv and uvx in the virtual environment
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# Copy dependency files first
COPY pyproject.toml poetry.lock ./
COPY pyproject.toml uv.lock ./
# Then copy the rest of the application code
COPY . .
RUN poetry lock && \
poetry install --all-extras && \
rm -rf $POETRY_CACHE_DIR
RUN uv sync --frozen --no-dev --all-extras --python 3.11
# Runtime stage
FROM ankane/pgvector:v0.5.1 AS runtime
@@ -48,8 +45,8 @@ FROM ankane/pgvector:v0.5.1 AS runtime
ARG NODE_VERSION=22
RUN apt-get update && \
# Install curl and Python
apt-get install -y curl python3 python3-venv && \
# Install curl, Python, and PostgreSQL client libraries
apt-get install -y curl python3 python3-venv libpq-dev && \
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \
apt-get install -y nodejs && \

View File

@@ -120,7 +120,7 @@ If your Letta server isn't running on `localhost` (for example, you deployed it
> _"Do I need to install Docker to use Letta?"_
No, you can install Letta using `pip` (via `pip install -U letta`), as well as from source (via `poetry install`). See instructions below.
No, you can install Letta using `pip` (via `pip install -U letta`), as well as from source (via `uv sync`). See instructions below.
> _"What's the difference between installing with `pip` vs `Docker`?"_

View File

@@ -7,7 +7,7 @@ See: https://docs.letta.com/quickstart
If you're using Letta Cloud, replace 'baseURL' with 'token'
See: https://docs.letta.com/api-reference/overview
Execute this script using `poetry run python3 example.py`
Execute this script using `uv run python3 example.py`
This will install `letta_client` and other dependencies.
"""

View File

@@ -5,7 +5,7 @@ try:
__version__ = version("letta")
except PackageNotFoundError:
# Fallback for development installations
__version__ = "0.11.5"
__version__ = "0.11.6"
if os.environ.get("LETTA_VERSION"):
__version__ = os.environ["LETTA_VERSION"]

View File

@@ -52,9 +52,9 @@ class JsonSchemaResponseFormat(ResponseFormat):
description="The JSON schema of the response.",
)
@field_validator("json_schema")
@classmethod
def validate_json_schema(cls, v: dict[str, Any]) -> Dict[str, Any]:
@field_validator("json_schema")
def validate_json_schema(cls, v: Dict[str, Any]) -> Dict[str, Any]:
"""Validate that the provided schema is a valid JSON schema."""
if "schema" not in v:
raise ValueError("JSON schema should include a schema property")

View File

@@ -1,12 +1,12 @@
#!/bin/sh
echo "Generating OpenAPI schema..."
# check if poetry is installed
if ! command -v poetry &> /dev/null
# check if uv is installed
if ! command -v uv &> /dev/null
then
echo "Poetry could not be found. Please install poetry to generate the OpenAPI schema."
echo "uv could not be found. Please install uv to generate the OpenAPI schema."
exit
fi
# generate OpenAPI schema
poetry run python -c 'from letta.server.rest_api.app import app, generate_openapi_schema; generate_openapi_schema(app);'
uv run python -c 'from letta.server.rest_api.app import app, generate_openapi_schema; generate_openapi_schema(app);'

View File

@@ -249,7 +249,7 @@ def compile_system_message(
append_icm_if_missing: bool = True,
template_format: Literal["f-string", "mustache", "jinja2"] = "f-string",
previous_message_count: int = 0,
archival_memory_size: int = 0,
archival_memory_size: int | None = 0,
tool_rules_solver: Optional[ToolRulesSolver] = None,
sources: Optional[List] = None,
max_files_open: Optional[int] = None,
@@ -281,7 +281,7 @@ def compile_system_message(
memory_metadata_string = PromptGenerator.compile_memory_metadata_block(
memory_edit_timestamp=in_context_memory_last_edit,
previous_message_count=previous_message_count,
archival_memory_size=archival_memory_size,
archival_memory_size=archival_memory_size or 0,
timezone=timezone,
)

View File

@@ -218,7 +218,7 @@ class LLMBatchManager:
query = query.where(LLMBatchJob.organization_id == actor.organization_id)
if weeks is not None:
cutoff_datetime = datetime.datetime.utcnow() - datetime.timedelta(weeks=weeks)
cutoff_datetime = datetime.datetime.now(datetime.UTC) - datetime.timedelta(weeks=weeks)
query = query.where(LLMBatchJob.created_at >= cutoff_datetime)
if batch_size is not None:

View File

@@ -249,7 +249,6 @@ class ToolExecutionSandbox:
logger.error(f"Executing tool {self.tool_name} has an unexpected error: {e}")
raise e
@trace_method
def run_local_dir_sandbox_directly(
self,
sbx_config: SandboxConfig,

View File

@@ -155,7 +155,6 @@ class AsyncToolSandboxLocal(AsyncToolSandboxBase):
if not settings.debug:
await asyncio.to_thread(os.remove, temp_file_path)
@trace_method
async def _prepare_venv(self, local_configs, venv_path: str, env: Dict[str, str]):
"""
Prepare virtual environment asynchronously (in a background thread).
@@ -179,7 +178,6 @@ class AsyncToolSandboxLocal(AsyncToolSandboxBase):
)
log_event(name="finish install_pip_requirements_for_sandbox", attributes={"local_configs": local_configs.model_dump_json()})
@trace_method
async def _execute_tool_subprocess(
self, sbx_config, python_executable: str, temp_file_path: str, env: Dict[str, str], cwd: str
) -> ToolExecutionResult:

View File

@@ -4,7 +4,7 @@ models=("gpt-4-0613" "gpt-3.5-turbo-1106" "gpt-4-1106-preview")
## run letta eval
for model in "${models[@]}";
do
poetry run python icml_experiments/doc_qa_task/llm_judge_doc_qa.py --file results/doc_qa_results_model_${model}.json
uv run python icml_experiments/doc_qa_task/llm_judge_doc_qa.py --file results/doc_qa_results_model_${model}.json
done
# Iterate over each model
@@ -13,6 +13,6 @@ for model in "${models[@]}"; do
for doc in "${docs[@]}"; do
# Construct and run the command
echo "Running for model $model with $doc docs..."
poetry run python icml_experiments/doc_qa_task/llm_judge_doc_qa.py --file results/doc_qa_baseline_model_${model}_num_docs_${doc}.json --baseline
uv run python icml_experiments/doc_qa_task/llm_judge_doc_qa.py --file results/doc_qa_baseline_model_${model}_num_docs_${doc}.json --baseline
done
done

9207
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"lock": {
"executor": "@nxlv/python:run-commands",
"options": {
"command": "uv lock --no-update",
"command": "uv lock --no-upgrade",
"cwd": "apps/core"
}
},
@@ -26,7 +26,10 @@
"dev": {
"executor": "@nxlv/python:run-commands",
"options": {
"commands": ["./otel/start-otel-collector.sh", "uv run letta server"],
"commands": [
"./otel/start-otel-collector.sh",
"uv run letta server"
],
"parallel": true,
"cwd": "apps/core"
}

View File

@@ -1,6 +1,6 @@
[project]
name = "letta"
version = "0.10.0"
version = "0.11.6"
description = "Create LLM agents with long-term memory and custom tools"
authors = [
{name = "Letta Team", email = "contact@letta.com"},
@@ -45,7 +45,7 @@ dependencies = [
"llama-index>=0.12.2",
"llama-index-embeddings-openai>=0.3.1",
"anthropic>=0.49.0",
"letta_client>=0.1.276",
"letta_client>=0.1.277",
"openai>=1.99.9",
"opentelemetry-api==1.30.0",
"opentelemetry-sdk==1.30.0",
@@ -57,7 +57,7 @@ dependencies = [
"marshmallow-sqlalchemy>=1.4.1",
"datamodel-code-generator[http]>=0.25.0",
"mcp[cli]>=1.9.4",
"firecrawl-py==2.16.5",
"firecrawl-py>=2.8.0,<3.0.0",
"apscheduler>=3.11.0",
"aiomultiprocess>=0.9.1",
"matplotlib>=3.10.1",
@@ -69,178 +69,91 @@ dependencies = [
"orjson>=3.11.1",
]
[project.optional-dependencies]
postgres = ["pgvector>=0.2.3", "pg8000>=1.30.3", "psycopg2-binary>=2.9.10", "psycopg2>=2.9.10", "asyncpg>=0.30.0"]
redis = ["redis>=6.2.0"]
pinecone = ["pinecone[asyncio]>=7.3.0"]
dev = ["pytest>=8.0.0", "pytest-asyncio>=0.24.0", "pexpect>=4.9.0", "black>=24.2.0", "pre-commit>=3.5.0", "pyright>=1.1.347", "pytest-order>=1.2.0", "autoflake>=2.3.0", "isort>=5.13.2", "locust>=2.31.5"]
experimental = ["uvloop>=0.21.0; sys_platform != 'win32'", "granian[reload]>=2.3.2", "google-cloud-profiler>=4.1.0"]
server = ["websockets>=12.0", "fastapi>=0.115.6", "uvicorn>=0.24.0.post1"]
cloud-tool-sandbox = ["e2b-code-interpreter==1.5.2", "modal>=1.1.0"]
external-tools = ["docker>=7.1.0", "langchain>=0.3.7", "wikipedia>=1.4.0", "langchain-community>=0.3.7", "firecrawl-py==2.16.5"]
tests = ["wikipedia>=1.4.0", "pytest-asyncio>=0.24.0"]
sqlite = ["aiosqlite>=0.21.0", "sqlite-vec>=0.1.7a2"]
bedrock = ["boto3>=1.36.24", "aioboto3>=14.3.0"]
google = ["google-genai>=1.15.0"]
desktop = ["pyright>=1.1.347", "fastapi>=0.115.6", "uvicorn>=0.24.0.post1", "docker>=7.1.0", "langchain>=0.3.7", "wikipedia>=1.4.0", "langchain-community>=0.3.7", "locust>=2.31.5", "sqlite-vec>=0.1.7a2", "pgvector>=0.2.3"]
all = ["pgvector>=0.2.3", "turbopuffer>=0.5.17", "pg8000>=1.30.3", "psycopg2-binary>=2.9.10", "psycopg2>=2.9.10", "pytest", "pytest-asyncio>=0.24.0", "pexpect>=4.9.0", "black>=24.2.0", "pre-commit>=3.5.0", "pyright>=1.1.347", "pytest-order>=1.2.0", "autoflake>=2.3.0", "isort>=5.13.2", "fastapi>=0.115.6", "uvicorn>=0.24.0.post1", "docker>=7.1.0", "langchain>=0.3.7", "wikipedia>=1.4.0", "langchain-community>=0.3.7", "locust>=2.31.5", "uvloop>=0.21.0; sys_platform != 'win32'", "granian[reload]>=2.3.2", "redis>=6.2.0", "pinecone[asyncio]>=7.3.0", "google-cloud-profiler>=4.1.0"]
[project.scripts]
letta = "letta.main:app"
[tool.poetry]
name = "letta"
version = "0.11.5"
packages = [
{include = "letta"},
[project.optional-dependencies]
# ====== Databases ======
postgres = [
"pgvector>=0.2.3",
"pg8000>=1.30.3",
"psycopg2-binary>=2.9.10",
"psycopg2>=2.9.10",
"asyncpg>=0.30.0",
]
description = "Create LLM agents with long-term memory and custom tools"
authors = [
"Letta Team <contact@letta.com>",
redis = ["redis>=6.2.0"]
pinecone = ["pinecone[asyncio]>=7.3.0"]
sqlite = ["aiosqlite>=0.21.0", "sqlite-vec>=0.1.7a2"]
# ====== Server ======
experimental = ["uvloop>=0.21.0", "granian[uvloop,reload]>=2.3.2", "google-cloud-profiler>=4.1.0"]
server = [
"websockets",
"fastapi>=0.115.6",
"uvicorn>=0.24.0.post1",
]
license = "Apache License"
readme = "README.md"
[tool.poetry.scripts]
letta = "letta.main:app"
# ====== LLM Providers ======
bedrock = [
"boto3>=1.36.24",
"aioboto3>=14.3.0",
]
google = ["google-genai>=1.15.0"]
[tool.poetry.dependencies]
python = "<3.14,>=3.11"
typer = "^0.15.2"
questionary = "^2.0.1"
pytz = "^2023.3.post1"
tqdm = "^4.66.1"
black = {extras = ["jupyter"], version = "^24.2.0"}
setuptools = "^70"
prettytable = "^3.9.0"
pgvector = { version = "^0.2.3", optional = true }
pre-commit = {version = "^3.5.0", optional = true }
pg8000 = {version = "^1.30.3", optional = true}
docstring-parser = ">=0.16,<0.17"
httpx = "^0.28.0"
numpy = "^2.1.0"
demjson3 = "^3.0.6"
pyyaml = "^6.0.1"
sqlalchemy-json = "^0.7.0"
fastapi = { version = "^0.115.6", optional = true}
uvicorn = {version = "^0.24.0.post1", optional = true}
pydantic = "^2.10.6"
html2text = "^2020.1.16"
sqlalchemy = {extras = ["asyncio"], version = "^2.0.41"}
pexpect = {version = "^4.9.0", optional = true}
pyright = {version = "^1.1.347", optional = true}
#pymilvus = {version ="^2.4.3", optional = true}
python-box = "^7.1.1"
sqlmodel = "^0.0.16"
autoflake = {version = "^2.3.0", optional = true}
python-multipart = "^0.0.19"
sqlalchemy-utils = "^0.41.2"
pytest-order = {version = "^1.2.0", optional = true}
pytest-asyncio = {version = "^0.24.0", optional = true}
pydantic-settings = "^2.2.1"
httpx-sse = "^0.4.0"
isort = { version = "^5.13.2", optional = true }
docker = {version = "^7.1.0", optional = true}
nltk = "^3.8.1"
jinja2 = "^3.1.5"
locust = {version = "^2.31.5", optional = true}
wikipedia = {version = "^1.4.0", optional = true}
composio-core = "^0.7.7"
alembic = "^1.13.3"
pyhumps = "^3.8.0"
psycopg2 = {version = "^2.9.10", optional = true}
psycopg2-binary = {version = "^2.9.10", optional = true}
pathvalidate = "^3.2.1"
langchain-community = {version = "^0.3.7", optional = true}
langchain = {version = "^0.3.7", optional = true}
sentry-sdk = {extras = ["fastapi"], version = "2.19.1"}
rich = "^13.9.4"
brotli = "^1.1.0"
grpcio = "^1.68.1"
grpcio-tools = "^1.68.1"
llama-index = "^0.12.2"
llama-index-embeddings-openai = "^0.3.1"
e2b-code-interpreter = {version = "^1.0.3", optional = true}
anthropic = "^0.49.0"
letta_client = "^0.1.277"
openai = "^1.99.9"
opentelemetry-api = "1.30.0"
opentelemetry-sdk = "1.30.0"
opentelemetry-instrumentation-requests = "0.51b0"
opentelemetry-instrumentation-sqlalchemy = "0.51b0"
opentelemetry-exporter-otlp = "1.30.0"
google-genai = {version = "^1.15.0", optional = true}
faker = "^36.1.0"
colorama = "^0.4.6"
marshmallow-sqlalchemy = "^1.4.1"
boto3 = {version = "^1.36.24", optional = true}
datamodel-code-generator = {extras = ["http"], version = "^0.25.0"}
mcp = {extras = ["cli"], version = "^1.9.4"}
firecrawl-py = "^2.8.0"
apscheduler = "^3.11.0"
aiomultiprocess = "^0.9.1"
matplotlib = "^3.10.1"
asyncpg = {version = "^0.30.0", optional = true}
tavily-python = "^0.7.2"
mistralai = "^1.8.1"
uvloop = {version = "^0.21.0", optional = true, markers = "sys_platform != 'win32'"}
granian = {version = "^2.3.2", extras = ["reload"], optional = true}
redis = {version = "^6.2.0", optional = true}
structlog = "^25.4.0"
certifi = "^2025.6.15"
aioboto3 = {version = "^14.3.0", optional = true}
pinecone = {extras = ["asyncio"], version = "^7.3.0", optional = true}
markitdown = {extras = ["docx", "pdf", "pptx"], version = "^0.1.2"}
google-cloud-profiler = {version = "^4.1.0", optional = true}
sqlite-vec = {version = "^0.1.7a2", optional = true}
orjson = "^3.11.1"
modal = {version = "^1.1.0", optional = true}
turbopuffer = {version = "^0.5.17", optional = true}
# ====== Development ======
dev = [
"pytest",
"pytest-asyncio>=0.24.0",
"pytest-order>=1.2.0",
"pytest-mock>=3.14.0",
"pytest-json-report>=1.5.0",
"pexpect>=4.9.0",
"black[jupyter]>=24.4.2",
"pre-commit>=3.5.0",
"pyright>=1.1.347",
"autoflake>=2.3.0",
"isort>=5.13.2",
"ipykernel>=6.29.5",
"ipdb>=0.13.13",
]
# ====== Other ======
cloud-tool-sandbox = ["e2b-code-interpreter>=1.0.3"] # TODO: make this more explicitly e2b
modal = ["modal>=1.1.0"]
external-tools = [
"docker>=7.1.0",
"langchain>=0.3.7",
"wikipedia>=1.4.0",
"langchain-community>=0.3.7",
"firecrawl-py>=2.8.0,<3.0.0",
"turbopuffer>=0.5.17",
]
desktop = [
"websockets",
"fastapi>=0.115.6",
"uvicorn>=0.24.0.post1",
"docker>=7.1.0",
"langchain>=0.3.7",
"wikipedia>=1.4.0",
"langchain-community>=0.3.7",
"locust>=2.31.5",
"aiosqlite>=0.21.0",
"sqlite-vec>=0.1.7a2",
"pgvector>=0.2.3",
]
[tool.poetry.extras]
postgres = ["pgvector", "pg8000", "psycopg2-binary", "psycopg2", "asyncpg"]
redis = ["redis"]
pinecone = ["pinecone"]
dev = ["pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "pyright", "pytest-order", "autoflake", "isort", "locust"]
experimental = ["uvloop", "granian", "google-cloud-profiler"]
server = ["websockets", "fastapi", "uvicorn"]
cloud-tool-sandbox = ["e2b-code-interpreter", "modal"] # TODO: split this up
external-tools = ["docker", "langchain", "wikipedia", "langchain-community", "firecrawl-py"]
tests = ["wikipedia"]
bedrock = ["boto3", "aioboto3"]
google = ["google-genai"]
desktop = ["pyright", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust", "sqlite-vec", "pgvector"]
all = ["pgvector", "turbopuffer", "pg8000", "psycopg2-binary", "psycopg2", "pytest", "pytest-asyncio", "pexpect", "black", "pre-commit", "pyright", "pytest-order", "autoflake", "isort", "websockets", "fastapi", "uvicorn", "docker", "langchain", "wikipedia", "langchain-community", "locust", "uvloop", "granian", "redis", "pinecone", "google-cloud-profiler"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.poetry.group.dev.dependencies]
black = "^24.4.2"
ipykernel = "^6.29.5"
ipdb = "^0.13.13"
pytest-mock = "^3.14.0"
[tool.poetry.group."dev,tests".dependencies]
pytest-json-report = "^1.5.0"
[tool.poetry.group.sqlite.dependencies]
aiosqlite = "^0.21.0"
# https://github.com/asg017/sqlite-vec/issues/148
sqlite-vec = "^0.1.7a2"
[tool.poetry.group.desktop.dependencies]
sqlite-vec = "^0.1.7a2"
[tool.hatch.build.targets.wheel]
packages = ["letta"]
[tool.black]
line-length = 140
target-version = ['py310', 'py311', 'py312', 'py313']
extend-exclude = "examples/*"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.isort]
profile = "black"
line_length = 140
@@ -248,3 +161,6 @@ multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
[tool.pytest.ini_options]
asyncio_mode = "auto"

View File

@@ -1033,12 +1033,11 @@ async def test_terminal_tool_rule_send_message_request_heartbeat_false(server, d
# Parse the arguments and check request_heartbeat
try:
arguments = json.loads(send_message_call.tool_call.arguments)
assert "request_heartbeat" in arguments, "request_heartbeat should be present in send_message arguments"
assert arguments["request_heartbeat"] is False, "request_heartbeat should be False for terminal tool rule"
print(f"✓ Agent '{agent_name}' correctly set request_heartbeat=False for terminal send_message")
except json.JSONDecodeError:
pytest.fail("Failed to parse tool call arguments as JSON")
assert "request_heartbeat" in arguments, "request_heartbeat should be present in send_message arguments"
assert arguments["request_heartbeat"] is False, "request_heartbeat should be False for terminal tool rule"
print(f"✓ Agent '{agent_name}' correctly set request_heartbeat=False for terminal send_message")
cleanup(server=server, agent_uuid=agent_name, actor=default_user)
finally:
cleanup(server=server, agent_uuid=agent_name, actor=default_user)

View File

@@ -759,7 +759,14 @@ def test_tool_call(
@pytest.mark.parametrize(
"llm_config",
TESTED_LLM_CONFIGS,
[
(
pytest.param(config, marks=pytest.mark.xfail(reason="Qwen image processing unstable - needs investigation"))
if config.model == "Qwen/Qwen2.5-72B-Instruct-Turbo"
else config
)
for config in TESTED_LLM_CONFIGS
],
ids=[c.model for c in TESTED_LLM_CONFIGS],
)
def test_url_image_input(
@@ -797,7 +804,14 @@ def test_url_image_input(
@pytest.mark.parametrize(
"llm_config",
TESTED_LLM_CONFIGS,
[
(
pytest.param(config, marks=pytest.mark.xfail(reason="Qwen image processing unstable - needs investigation"))
if config.model == "Qwen/Qwen2.5-72B-Instruct-Turbo"
else config
)
for config in TESTED_LLM_CONFIGS
],
ids=[c.model for c in TESTED_LLM_CONFIGS],
)
def test_base64_image_input(
@@ -1568,6 +1582,7 @@ def test_async_greeting_with_callback_url(
assert headers.get("Content-Type") == "application/json", "Callback should have JSON content type"
@pytest.mark.flaky(max_runs=2)
@pytest.mark.parametrize(
"llm_config",
TESTED_LLM_CONFIGS,

View File

@@ -3,6 +3,7 @@ pythonpath = /letta
testpaths = /tests
asyncio_mode = auto
asyncio_default_fixture_loop_scope = function
asyncio_default_test_loop_scope = function
filterwarnings =
ignore::pytest.PytestRemovedIn9Warning
# suppresses the warnings we see with the event_loop fixture

View File

@@ -733,6 +733,7 @@ def validate_id_format(schema: AgentFileSchema) -> bool:
class TestFileExport:
"""Test file export functionality with comprehensive validation"""
@pytest.mark.asyncio(loop_scope="session")
async def test_basic_file_export(self, default_user, agent_serialization_manager, agent_with_files):
"""Test basic file export functionality"""
agent_id, source_id, file_id = agent_with_files
@@ -755,6 +756,7 @@ class TestFileExport:
assert file_agent.file_id == exported.files[0].id
assert file_agent.source_id == exported.sources[0].id
@pytest.mark.asyncio(loop_scope="session")
async def test_multiple_files_per_source(self, server, default_user, agent_serialization_manager):
"""Test export with multiple files from the same source"""
source = await create_test_source(server, "multi-file-source", default_user)
@@ -781,6 +783,7 @@ class TestFileExport:
assert file_agent.file_id in file_ids
assert file_agent.source_id == source_id
@pytest.mark.asyncio(loop_scope="session")
async def test_multiple_sources_export(self, server, default_user, agent_serialization_manager):
"""Test export with files from multiple sources"""
source1 = await create_test_source(server, "source-1", default_user)
@@ -802,6 +805,7 @@ class TestFileExport:
for file_schema in exported.files:
assert file_schema.source_id in source_ids
@pytest.mark.asyncio(loop_scope="session")
async def test_cross_agent_file_deduplication(self, server, default_user, agent_serialization_manager):
"""Test that files shared across agents are deduplicated in export"""
source = await create_test_source(server, "shared-source", default_user)
@@ -825,6 +829,7 @@ class TestFileExport:
assert file_agent.file_id == file_id
assert file_agent.source_id == source_id
@pytest.mark.asyncio(loop_scope="session")
async def test_file_agent_relationship_preservation(self, server, default_user, agent_serialization_manager):
"""Test that file-agent relationship details are preserved"""
source = await create_test_source(server, "test-source", default_user)
@@ -841,6 +846,7 @@ class TestFileExport:
assert file_agent.is_open is True
assert hasattr(file_agent, "last_accessed_at")
@pytest.mark.asyncio(loop_scope="session")
async def test_id_remapping_consistency(self, server, default_user, agent_serialization_manager):
"""Test that ID remapping is consistent across all references"""
source = await create_test_source(server, "consistency-source", default_user)
@@ -859,6 +865,7 @@ class TestFileExport:
assert file_agent.file_id == file_schema.id
assert file_agent.source_id == source_schema.id
@pytest.mark.asyncio(loop_scope="session")
async def test_empty_file_relationships(self, server, default_user, agent_serialization_manager):
"""Test export of agent with no file relationships"""
agent_create = CreateAgent(
@@ -877,6 +884,7 @@ class TestFileExport:
agent_schema = exported.agents[0]
assert len(agent_schema.files_agents) == 0
@pytest.mark.asyncio(loop_scope="session")
async def test_file_content_inclusion_in_export(self, default_user, agent_serialization_manager, agent_with_files):
"""Test that file content is included in export"""
agent_id, source_id, file_id = agent_with_files
@@ -985,6 +993,7 @@ class TestAgentFileExport:
with pytest.raises(AgentFileExportError): # Should raise AgentFileExportError for non-existent agent
await agent_serialization_manager.export(["non-existent-id"], default_user)
@pytest.mark.asyncio(loop_scope="session")
async def test_revision_id_automatic_setting(self, agent_serialization_manager, test_agent, default_user):
"""Test that revision_id is automatically set to the latest alembic revision."""
agent_file = await agent_serialization_manager.export([test_agent.id], default_user)

View File

@@ -36,7 +36,7 @@ def swap_letta_config():
def test_letta_run_create_new_agent(swap_letta_config):
child = pexpect.spawn("poetry run letta run", encoding="utf-8")
child = pexpect.spawn("uv run letta run", encoding="utf-8")
# Start the letta run command
child.logfile = sys.stdout
child.expect("Creating new agent", timeout=20)
@@ -79,7 +79,7 @@ def test_letta_run_create_new_agent(swap_letta_config):
def test_letta_version_prints_only_version(swap_letta_config):
# Start the letta version command
output = pexpect.run("poetry run letta version", encoding="utf-8")
output = pexpect.run("uv run letta version", encoding="utf-8")
# Remove ANSI escape sequences and whitespace
output = re.sub(r"\x1b\[[0-9;]*[mK]", "", output).strip()

View File

@@ -134,7 +134,7 @@ async def agents(server, weather_tool):
)
@pytest.fixture
@pytest.fixture(scope="function")
def batch_requests(agents):
"""
Create batch requests for each test agent.
@@ -151,7 +151,7 @@ def batch_requests(agents):
]
@pytest.fixture
@pytest.fixture(scope="function")
def step_state_map(agents):
"""
Create a mapping of agent IDs to their step states.
@@ -264,7 +264,7 @@ def create_failed_response(custom_id: str) -> BetaMessageBatchIndividualResponse
)
@pytest.fixture
@pytest.fixture(scope="function")
def dummy_batch_response():
"""
Create a minimal dummy batch response similar to what Anthropic would return.

View File

@@ -561,7 +561,6 @@ def server():
@pytest.fixture
@pytest.mark.asyncio
async def default_archive(server, default_user):
archive = await server.archive_manager.create_archive_async("test", actor=default_user)
yield archive
@@ -700,14 +699,6 @@ def letta_batch_job(server: SyncServer, default_user) -> Job:
return server.job_manager.create_job(BatchJob(user_id=default_user.id), actor=default_user)
@pytest.fixture(scope="session")
def event_loop(request):
"""Create an instance of the default event loop for each test case."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture
async def file_attachment(server, default_user, sarah_agent, default_file):
assoc, closed_files = await server.file_agent_manager.attach_file(
@@ -735,7 +726,6 @@ async def another_file(server, default_source, default_user, default_organizatio
# ======================================================================================================================
# AgentManager Tests - Basic
# ======================================================================================================================
@pytest.mark.asyncio
async def test_validate_agent_exists_async(server: SyncServer, comprehensive_test_agent_fixture, default_user):
"""Test the validate_agent_exists_async helper function"""
created_agent, _ = comprehensive_test_agent_fixture
@@ -1013,9 +1003,8 @@ def set_letta_environment(request):
os.environ.pop("LETTA_ENVIRONMENT", None)
@pytest.mark.asyncio
async def test_get_context_window_basic(
server: SyncServer, comprehensive_test_agent_fixture, default_user, default_file, event_loop, set_letta_environment
server: SyncServer, comprehensive_test_agent_fixture, default_user, default_file, set_letta_environment
):
# Test agent creation
created_agent, create_agent_request = comprehensive_test_agent_fixture
@@ -1124,10 +1113,7 @@ async def test_create_agent_with_json_in_system_message(server: SyncServer, defa
server.agent_manager.delete_agent(agent_id=agent_state.id, actor=default_user)
@pytest.mark.asyncio
async def test_update_agent(
server: SyncServer, comprehensive_test_agent_fixture, other_tool, other_source, other_block, default_user, event_loop
):
async def test_update_agent(server: SyncServer, comprehensive_test_agent_fixture, other_tool, other_source, other_block, default_user):
agent, _ = comprehensive_test_agent_fixture
update_agent_request = UpdateAgent(
name="train_agent",
@@ -1615,21 +1601,18 @@ async def test_bulk_detach_tools_nonexistent_agent(server: SyncServer, print_too
await server.agent_manager.bulk_detach_tools_async(agent_id=nonexistent_agent_id, tool_ids=tool_ids, actor=default_user)
@pytest.mark.asyncio
async def test_attach_tool_nonexistent_agent(server: SyncServer, print_tool, default_user):
"""Test attaching a tool to a nonexistent agent."""
with pytest.raises(NoResultFound):
await server.agent_manager.attach_tool_async(agent_id="nonexistent-agent-id", tool_id=print_tool.id, actor=default_user)
@pytest.mark.asyncio
async def test_attach_tool_nonexistent_tool(server: SyncServer, sarah_agent, default_user):
"""Test attaching a nonexistent tool to an agent."""
with pytest.raises(NoResultFound):
await server.agent_manager.attach_tool_async(agent_id=sarah_agent.id, tool_id="nonexistent-tool-id", actor=default_user)
@pytest.mark.asyncio
async def test_detach_tool_nonexistent_agent(server: SyncServer, print_tool, default_user):
"""Test detaching a tool from a nonexistent agent."""
with pytest.raises(NoResultFound):
@@ -2023,7 +2006,6 @@ async def test_list_attached_agents(server: SyncServer, sarah_agent, charles_age
assert charles_agent.id in [a.id for a in attached_agents]
@pytest.mark.asyncio
async def test_list_attached_agents_nonexistent_source(server: SyncServer, default_user):
"""Test listing agents for a nonexistent source."""
with pytest.raises(NoResultFound):
@@ -2824,10 +2806,7 @@ def mock_embed_model(mock_embeddings):
return mock_model
@pytest.mark.asyncio
async def test_agent_list_passages_vector_search(
server, default_user, sarah_agent, default_source, default_file, event_loop, mock_embed_model
):
async def test_agent_list_passages_vector_search(server, default_user, sarah_agent, default_source, default_file, mock_embed_model):
"""Test vector search functionality of agent passages"""
embed_model = mock_embed_model
@@ -3053,9 +3032,8 @@ def test_passage_get_by_id(server: SyncServer, agent_passage_fixture, source_pas
assert retrieved.text == source_passage_fixture.text
@pytest.mark.asyncio
async def test_passage_cascade_deletion(
server: SyncServer, agent_passage_fixture, source_passage_fixture, default_user, default_source, sarah_agent, event_loop
server: SyncServer, agent_passage_fixture, source_passage_fixture, default_user, default_source, sarah_agent
):
"""Test that passages are deleted when their parent (agent or source) is deleted."""
# Verify passages exist
@@ -3582,8 +3560,7 @@ async def test_update_user(server: SyncServer):
assert user.organization_id == test_org.id
@pytest.mark.asyncio
async def test_user_caching(server: SyncServer, event_loop, default_user, performance_pct=0.4):
async def test_user_caching(server: SyncServer, default_user, performance_pct=0.4):
if isinstance(await get_redis_client(), NoopAsyncRedisClient):
pytest.skip("redis not available")
# Invalidate previous cache behavior.
@@ -3859,7 +3836,6 @@ async def test_upsert_base_tools(server: SyncServer, default_user):
assert t.json_schema
@pytest.mark.asyncio
@pytest.mark.parametrize(
"tool_type,expected_names",
[
@@ -3886,7 +3862,6 @@ async def test_upsert_filtered_base_tools(server: SyncServer, default_user, tool
assert all(t.tool_type == tool_type for t in tools)
@pytest.mark.asyncio
async def test_upsert_multiple_tool_types(server: SyncServer, default_user):
allowed = {ToolType.LETTA_CORE, ToolType.LETTA_BUILTIN, ToolType.LETTA_FILES_CORE}
tools = await server.tool_manager.upsert_base_tools_async(actor=default_user, allowed_types=allowed)
@@ -3897,13 +3872,11 @@ async def test_upsert_multiple_tool_types(server: SyncServer, default_user):
assert all(t.tool_type in allowed for t in tools)
@pytest.mark.asyncio
async def test_upsert_base_tools_with_empty_type_filter(server: SyncServer, default_user):
tools = await server.tool_manager.upsert_base_tools_async(actor=default_user, allowed_types=set())
assert tools == []
@pytest.mark.asyncio
async def test_bulk_upsert_tools_async(server: SyncServer, default_user):
"""Test bulk upserting multiple tools at once"""
# create multiple test tools
@@ -3960,7 +3933,6 @@ async def test_bulk_upsert_tools_async(server: SyncServer, default_user):
assert result[0].description is not None # should be auto-generated from docstring
@pytest.mark.asyncio
async def test_bulk_upsert_tools_name_conflict(server: SyncServer, default_user):
"""Test bulk upserting tools handles name+org_id unique constraint correctly"""
@@ -4003,7 +3975,6 @@ async def test_bulk_upsert_tools_name_conflict(server: SyncServer, default_user)
assert tools_with_name[0].id == original_id
@pytest.mark.asyncio
async def test_bulk_upsert_tools_mixed_create_update(server: SyncServer, default_user):
"""Test bulk upserting with mix of new tools and updates to existing ones"""
@@ -4270,13 +4241,11 @@ async def test_create_tool_with_pip_requirements(server: SyncServer, default_use
assert created_tool.pip_requirements[1].version is None
@pytest.mark.asyncio
async def test_create_tool_without_pip_requirements(server: SyncServer, print_tool):
# Verify that tools without pip_requirements have the field as None
assert print_tool.pip_requirements is None
@pytest.mark.asyncio
async def test_update_tool_pip_requirements(server: SyncServer, print_tool, default_user):
# Add pip requirements to existing tool
pip_reqs = [
@@ -4299,7 +4268,6 @@ async def test_update_tool_pip_requirements(server: SyncServer, print_tool, defa
assert updated_tool.pip_requirements[1].version is None
@pytest.mark.asyncio
async def test_update_tool_clear_pip_requirements(server: SyncServer, default_user, default_organization):
def test_tool_clear_deps():
"""
@@ -4345,7 +4313,6 @@ async def test_update_tool_clear_pip_requirements(server: SyncServer, default_us
assert updated_tool.pip_requirements == []
@pytest.mark.asyncio
async def test_pip_requirements_roundtrip(server: SyncServer, default_user, default_organization):
def roundtrip_test_tool():
"""
@@ -4604,7 +4571,6 @@ def test_create_block(server: SyncServer, default_user):
assert block.metadata == block_create.metadata
@pytest.mark.asyncio
async def test_batch_create_blocks_async(server: SyncServer, default_user):
"""Test batch creating multiple blocks at once"""
block_manager = BlockManager()
@@ -4884,10 +4850,7 @@ async def test_batch_create_multiple_blocks(server: SyncServer, default_user):
assert expected_labels.issubset(all_labels)
@pytest.mark.asyncio
async def test_bulk_update_skips_missing_and_truncates_then_returns_none(
server: SyncServer, default_user: PydanticUser, caplog, event_loop
):
async def test_bulk_update_skips_missing_and_truncates_then_returns_none(server: SyncServer, default_user: PydanticUser, caplog):
mgr = BlockManager()
# create one block with a small limit
@@ -4918,7 +4881,6 @@ async def test_bulk_update_skips_missing_and_truncates_then_returns_none(
assert reloaded.value == long_val[:5]
@pytest.mark.asyncio
@pytest.mark.skip(reason="TODO: implement for async")
async def test_bulk_update_return_hydrated_true(server: SyncServer, default_user: PydanticUser):
mgr = BlockManager()
@@ -4938,9 +4900,8 @@ async def test_bulk_update_return_hydrated_true(server: SyncServer, default_user
assert updated[0].value == "new-val"
@pytest.mark.asyncio
async def test_bulk_update_respects_org_scoping(
server: SyncServer, default_user: PydanticUser, other_user_different_org: PydanticUser, caplog, event_loop
server: SyncServer, default_user: PydanticUser, other_user_different_org: PydanticUser, caplog
):
mgr = BlockManager()
@@ -5587,7 +5548,6 @@ async def test_create_and_upsert_identity(server: SyncServer, default_user):
await server.identity_manager.delete_identity_async(identity_id=identity.id, actor=default_user)
@pytest.mark.asyncio
async def test_get_identities(server, default_user):
# Create identities to retrieve later
user = await server.identity_manager.create_identity_async(
@@ -5833,6 +5793,34 @@ async def test_get_set_blocks_for_identities(server: SyncServer, default_block,
await server.identity_manager.delete_identity_async(identity_id=identity.id, actor=default_user)
async def test_upsert_properties(server: SyncServer, default_user):
identity_create = IdentityCreate(
identifier_key="1234",
name="caren",
identity_type=IdentityType.user,
properties=[
IdentityProperty(key="email", value="caren@letta.com", type=IdentityPropertyType.string),
IdentityProperty(key="age", value=28, type=IdentityPropertyType.number),
],
)
identity = await server.identity_manager.create_identity_async(identity_create, actor=default_user)
properties = [
IdentityProperty(key="email", value="caren@gmail.com", type=IdentityPropertyType.string),
IdentityProperty(key="age", value="28", type=IdentityPropertyType.string),
IdentityProperty(key="test", value=123, type=IdentityPropertyType.number),
]
updated_identity = await server.identity_manager.upsert_identity_properties_async(
identity_id=identity.id,
properties=properties,
actor=default_user,
)
assert updated_identity.properties == properties
await server.identity_manager.delete_identity_async(identity_id=identity.id, actor=default_user)
# ======================================================================================================================
# SourceManager Tests - Sources
# ======================================================================================================================
@@ -5909,7 +5897,6 @@ async def test_create_source(server: SyncServer, default_user):
assert source.organization_id == default_user.organization_id
@pytest.mark.asyncio
async def test_create_sources_with_same_name_raises_error(server: SyncServer, default_user):
"""Test that creating sources with the same name raises an IntegrityError due to unique constraint."""
name = "Test Source"
@@ -5932,7 +5919,6 @@ async def test_create_sources_with_same_name_raises_error(server: SyncServer, de
await server.source_manager.create_source(source=source_pydantic, actor=default_user)
@pytest.mark.asyncio
async def test_update_source(server: SyncServer, default_user):
"""Test updating an existing source."""
source_pydantic = PydanticSource(name="Original Source", description="Original description", embedding_config=DEFAULT_EMBEDDING_CONFIG)
@@ -5948,7 +5934,6 @@ async def test_update_source(server: SyncServer, default_user):
assert updated_source.metadata == update_data.metadata
@pytest.mark.asyncio
async def test_delete_source(server: SyncServer, default_user):
"""Test deleting a source."""
source_pydantic = PydanticSource(
@@ -5992,7 +5977,6 @@ async def test_delete_attached_source(server: SyncServer, sarah_agent, default_u
assert agent is not None
@pytest.mark.asyncio
async def test_list_sources(server: SyncServer, default_user):
"""Test listing sources with pagination."""
# Create multiple sources
@@ -6019,7 +6003,6 @@ async def test_list_sources(server: SyncServer, default_user):
assert next_page[0].name != paginated_sources[0].name
@pytest.mark.asyncio
async def test_get_source_by_id(server: SyncServer, default_user):
"""Test retrieving a source by ID."""
source_pydantic = PydanticSource(
@@ -6036,7 +6019,6 @@ async def test_get_source_by_id(server: SyncServer, default_user):
assert retrieved_source.description == source.description
@pytest.mark.asyncio
async def test_get_source_by_name(server: SyncServer, default_user):
"""Test retrieving a source by name."""
source_pydantic = PydanticSource(
@@ -6052,7 +6034,6 @@ async def test_get_source_by_name(server: SyncServer, default_user):
assert retrieved_source.description == source.description
@pytest.mark.asyncio
async def test_update_source_no_changes(server: SyncServer, default_user):
"""Test update_source with no actual changes to verify logging and response."""
source_pydantic = PydanticSource(name="No Change Source", description="No changes", embedding_config=DEFAULT_EMBEDDING_CONFIG)
@@ -6068,7 +6049,6 @@ async def test_update_source_no_changes(server: SyncServer, default_user):
assert updated_source.description == source.description
@pytest.mark.asyncio
async def test_bulk_upsert_sources_async(server: SyncServer, default_user):
"""Test bulk upserting sources."""
sources_data = [
@@ -6105,7 +6085,6 @@ async def test_bulk_upsert_sources_async(server: SyncServer, default_user):
assert source.organization_id == default_user.organization_id
@pytest.mark.asyncio
async def test_bulk_upsert_sources_name_conflict(server: SyncServer, default_user):
"""Test bulk upserting sources with name conflicts."""
# Create an existing source
@@ -6152,7 +6131,6 @@ async def test_bulk_upsert_sources_name_conflict(server: SyncServer, default_use
assert new_source.description == "Completely new"
@pytest.mark.asyncio
async def test_bulk_upsert_sources_mixed_create_update(server: SyncServer, default_user):
"""Test bulk upserting with a mix of creates and updates."""
# Create some existing sources
@@ -6231,7 +6209,6 @@ async def test_bulk_upsert_sources_mixed_create_update(server: SyncServer, defau
# ======================================================================================================================
@pytest.mark.asyncio
async def test_get_file_by_id(server: SyncServer, default_user, default_source):
"""Test retrieving a file by ID."""
file_metadata = PydanticFileMetadata(
@@ -6253,7 +6230,6 @@ async def test_get_file_by_id(server: SyncServer, default_user, default_source):
assert retrieved_file.file_type == created_file.file_type
@pytest.mark.asyncio
async def test_create_and_retrieve_file_with_content(server, default_user, default_source, async_session):
text_body = "Line 1\nLine 2\nLine 3"
@@ -6282,7 +6258,6 @@ async def test_create_and_retrieve_file_with_content(server, default_user, defau
assert loaded.content == text_body
@pytest.mark.asyncio
async def test_create_file_without_content(server, default_user, default_source, async_session):
meta = PydanticFileMetadata(
file_name="no_body.txt",
@@ -6301,7 +6276,6 @@ async def test_create_file_without_content(server, default_user, default_source,
assert loaded.content is None
@pytest.mark.asyncio
async def test_lazy_raise_guard(server, default_user, default_source, async_session):
text_body = "lazy-raise"
@@ -6322,13 +6296,11 @@ async def test_lazy_raise_guard(server, default_user, default_source, async_sess
await orm.to_pydantic_async(include_content=True)
@pytest.mark.asyncio
async def test_list_files_content_none(server, default_user, default_source):
files = await server.file_manager.list_files(source_id=default_source.id, actor=default_user)
assert all(f.content is None for f in files)
@pytest.mark.asyncio
async def test_delete_cascades_to_content(server, default_user, default_source, async_session):
text_body = "to be deleted"
meta = PydanticFileMetadata(
@@ -6350,7 +6322,6 @@ async def test_delete_cascades_to_content(server, default_user, default_source,
assert await _count_file_content_rows(async_session, created.id) == 0
@pytest.mark.asyncio
async def test_get_file_by_original_name_and_source_found(server: SyncServer, default_user, default_source):
"""Test retrieving a file by original filename and source when it exists."""
original_filename = "test_original_file.txt"
@@ -6376,7 +6347,6 @@ async def test_get_file_by_original_name_and_source_found(server: SyncServer, de
assert retrieved_file.source_id == default_source.id
@pytest.mark.asyncio
async def test_get_file_by_original_name_and_source_not_found(server: SyncServer, default_user, default_source):
"""Test retrieving a file by original filename and source when it doesn't exist."""
non_existent_filename = "does_not_exist.txt"
@@ -6390,7 +6360,6 @@ async def test_get_file_by_original_name_and_source_not_found(server: SyncServer
assert retrieved_file is None
@pytest.mark.asyncio
async def test_get_file_by_original_name_and_source_different_sources(server: SyncServer, default_user, default_source):
"""Test that files with same original name in different sources are handled correctly."""
from letta.schemas.source import Source as PydanticSource
@@ -6448,7 +6417,6 @@ async def test_get_file_by_original_name_and_source_different_sources(server: Sy
assert retrieved_file_2.source_id == second_source.id
@pytest.mark.asyncio
async def test_get_file_by_original_name_and_source_ignores_deleted(server: SyncServer, default_user, default_source):
"""Test that deleted files are ignored when searching by original name and source."""
original_filename = "to_be_deleted.txt"
@@ -6481,7 +6449,6 @@ async def test_get_file_by_original_name_and_source_ignores_deleted(server: Sync
assert retrieved_file_after_delete is None
@pytest.mark.asyncio
async def test_list_files(server: SyncServer, default_user, default_source):
"""Test listing files with pagination."""
# Create multiple files
@@ -6510,7 +6477,6 @@ async def test_list_files(server: SyncServer, default_user, default_source):
assert next_page[0].file_name != paginated_files[0].file_name
@pytest.mark.asyncio
async def test_delete_file(server: SyncServer, default_user, default_source):
"""Test deleting a file."""
file_metadata = PydanticFileMetadata(
@@ -6529,7 +6495,6 @@ async def test_delete_file(server: SyncServer, default_user, default_source):
assert len(files) == 0
@pytest.mark.asyncio
async def test_update_file_status_basic(server, default_user, default_source):
"""Update processing status and error message for a file."""
meta = PydanticFileMetadata(
@@ -6561,7 +6526,6 @@ async def test_update_file_status_basic(server, default_user, default_source):
assert updated.error_message == "Parse failed"
@pytest.mark.asyncio
async def test_update_file_status_error_only(server, default_user, default_source):
"""Update just the error message, leave status unchanged."""
meta = PydanticFileMetadata(
@@ -6582,7 +6546,6 @@ async def test_update_file_status_error_only(server, default_user, default_sourc
assert updated.processing_status == FileProcessingStatus.PENDING # default from creation
@pytest.mark.asyncio
async def test_update_file_status_with_chunks(server, default_user, default_source):
"""Update chunk progress fields along with status."""
meta = PydanticFileMetadata(
@@ -7084,7 +7047,6 @@ async def test_same_state_transitions_allowed(server, default_user, default_sour
assert updated.total_chunks == 10
@pytest.mark.asyncio
async def test_upsert_file_content_basic(server: SyncServer, default_user, default_source, async_session):
"""Test creating and updating file content with upsert_file_content()."""
initial_text = "Initial content"
@@ -7130,7 +7092,6 @@ async def test_upsert_file_content_basic(server: SyncServer, default_user, defau
assert orm_file.updated_at >= orm_file.created_at
@pytest.mark.asyncio
async def test_get_organization_sources_metadata(server, default_user):
"""Test getting organization sources metadata with aggregated file information."""
# Create test sources
@@ -7692,7 +7653,6 @@ async def test_list_jobs_filter_by_type(server: SyncServer, default_user, defaul
assert jobs[0].id == run.id
@pytest.mark.asyncio
async def test_e2e_job_callback(monkeypatch, server: SyncServer, default_user):
"""Test that job callbacks are properly dispatched when a job is completed."""
captured = {}
@@ -8784,9 +8744,8 @@ async def test_update_batch_status(server, default_user, dummy_beta_message_batc
assert last_polled_at >= before
@pytest.mark.asyncio
async def test_create_and_get_batch_item(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -8811,7 +8770,6 @@ async def test_create_and_get_batch_item(
assert fetched.id == item.id
@pytest.mark.asyncio
async def test_update_batch_item(
server,
default_user,
@@ -8821,7 +8779,6 @@ async def test_update_batch_item(
dummy_step_state,
dummy_successful_response,
letta_batch_job,
event_loop,
):
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -8855,9 +8812,8 @@ async def test_update_batch_item(
assert updated.batch_request_result == dummy_successful_response
@pytest.mark.asyncio
async def test_delete_batch_item(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -8945,7 +8901,6 @@ async def test_bulk_update_batch_statuses(server, default_user, dummy_beta_messa
assert updated.latest_polling_response == dummy_beta_message_batch
@pytest.mark.asyncio
async def test_bulk_update_batch_items_results_by_agent(
server,
default_user,
@@ -8955,7 +8910,6 @@ async def test_bulk_update_batch_items_results_by_agent(
dummy_step_state,
dummy_successful_response,
letta_batch_job,
event_loop,
):
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -8980,9 +8934,8 @@ async def test_bulk_update_batch_items_results_by_agent(
assert updated.batch_request_result == dummy_successful_response
@pytest.mark.asyncio
async def test_bulk_update_batch_items_step_status_by_agent(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -9006,9 +8959,8 @@ async def test_bulk_update_batch_items_step_status_by_agent(
assert updated.step_status == AgentStepStatus.resumed
@pytest.mark.asyncio
async def test_list_batch_items_limit_and_filter(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -9033,9 +8985,8 @@ async def test_list_batch_items_limit_and_filter(
assert len(limited_items) == 2
@pytest.mark.asyncio
async def test_list_batch_items_pagination(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
# Create a batch job.
batch = await server.batch_manager.create_llm_batch_job_async(
@@ -9098,9 +9049,8 @@ async def test_list_batch_items_pagination(
assert empty_page == [], "Expected an empty list when cursor is after the last item"
@pytest.mark.asyncio
async def test_bulk_update_batch_items_request_status_by_agent(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
# Create a batch job
batch = await server.batch_manager.create_llm_batch_job_async(
@@ -9129,14 +9079,12 @@ async def test_bulk_update_batch_items_request_status_by_agent(
assert updated.request_status == JobStatus.expired
@pytest.mark.asyncio
async def test_bulk_update_nonexistent_items_should_error(
server,
default_user,
dummy_beta_message_batch,
dummy_successful_response,
letta_batch_job,
event_loop,
):
# Create a batch job
batch = await server.batch_manager.create_llm_batch_job_async(
@@ -9172,10 +9120,7 @@ async def test_bulk_update_nonexistent_items_should_error(
)
@pytest.mark.asyncio
async def test_bulk_update_nonexistent_items(
server, default_user, dummy_beta_message_batch, dummy_successful_response, letta_batch_job, event_loop
):
async def test_bulk_update_nonexistent_items(server, default_user, dummy_beta_message_batch, dummy_successful_response, letta_batch_job):
# Create a batch job
batch = await server.batch_manager.create_llm_batch_job_async(
llm_provider=ProviderType.anthropic,
@@ -9210,9 +9155,8 @@ async def test_bulk_update_nonexistent_items(
)
@pytest.mark.asyncio
async def test_create_batch_items_bulk(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
# Create a batch job
llm_batch_job = await server.batch_manager.create_llm_batch_job_async(
@@ -9264,9 +9208,8 @@ async def test_create_batch_items_bulk(
assert fetched.id in created_ids
@pytest.mark.asyncio
async def test_count_batch_items(
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job, event_loop
server, default_user, sarah_agent, dummy_beta_message_batch, dummy_llm_config, dummy_step_state, letta_batch_job
):
# Create a batch job first.
batch = await server.batch_manager.create_llm_batch_job_async(
@@ -9606,7 +9549,6 @@ async def test_mcp_server_delete_removes_all_sessions_for_url_and_user(server, d
# ======================================================================================================================
@pytest.mark.asyncio
async def test_attach_creates_association(server, default_user, sarah_agent, default_file):
assoc, closed_files = await server.file_agent_manager.attach_file(
agent_id=sarah_agent.id,
@@ -9629,7 +9571,6 @@ async def test_attach_creates_association(server, default_user, sarah_agent, def
assert file_blocks[0].label == default_file.file_name
@pytest.mark.asyncio
async def test_attach_is_idempotent(server, default_user, sarah_agent, default_file):
a1, closed_files = await server.file_agent_manager.attach_file(
agent_id=sarah_agent.id,
@@ -9664,7 +9605,6 @@ async def test_attach_is_idempotent(server, default_user, sarah_agent, default_f
assert file_blocks[0].label == default_file.file_name
@pytest.mark.asyncio
async def test_update_file_agent(server, file_attachment, default_user):
updated = await server.file_agent_manager.update_file_agent_by_id(
agent_id=file_attachment.agent_id,
@@ -9677,7 +9617,6 @@ async def test_update_file_agent(server, file_attachment, default_user):
assert updated.visible_content == "updated"
@pytest.mark.asyncio
async def test_update_file_agent_by_file_name(server, file_attachment, default_user):
updated = await server.file_agent_manager.update_file_agent_by_name(
agent_id=file_attachment.agent_id,
@@ -9755,7 +9694,6 @@ async def test_file_agent_line_tracking(server, default_user, sarah_agent, defau
assert previous_ranges == {file.file_name: (2, 4)} # Should capture the previous range
@pytest.mark.asyncio
async def test_mark_access(server, file_attachment, default_user):
old_ts = file_attachment.last_accessed_at
if USING_SQLITE:
@@ -9776,7 +9714,6 @@ async def test_mark_access(server, file_attachment, default_user):
assert refreshed.last_accessed_at > old_ts
@pytest.mark.asyncio
async def test_list_files_and_agents(
server,
default_user,
@@ -10046,7 +9983,6 @@ async def test_detach_file(server, file_attachment, default_user):
assert res is None
@pytest.mark.asyncio
async def test_detach_file_bulk(
server,
default_user,
@@ -10135,7 +10071,6 @@ async def test_detach_file_bulk(
assert deleted_count == 0
@pytest.mark.asyncio
async def test_org_scoping(
server,
default_user,
@@ -10165,7 +10100,6 @@ async def test_org_scoping(
# ======================================================================================================================
@pytest.mark.asyncio
async def test_mark_access_bulk(server, default_user, sarah_agent, default_source):
"""Test that mark_access_bulk updates last_accessed_at for multiple files."""
import time
@@ -10218,7 +10152,6 @@ async def test_mark_access_bulk(server, default_user, sarah_agent, default_sourc
assert fa.last_accessed_at == initial_times[file.file_name], f"File {file.file_name} should not have updated timestamp"
@pytest.mark.asyncio
async def test_lru_eviction_on_attach(server, default_user, sarah_agent, default_source):
"""Test that attaching files beyond max_files_open triggers LRU eviction."""
import time
@@ -10288,7 +10221,6 @@ async def test_lru_eviction_on_attach(server, default_user, sarah_agent, default
assert open_file_names == expected_open
@pytest.mark.asyncio
async def test_lru_eviction_on_open_file(server, default_user, sarah_agent, default_source):
"""Test that opening a file beyond max_files_open triggers LRU eviction."""
import time
@@ -10377,7 +10309,6 @@ async def test_lru_eviction_on_open_file(server, default_user, sarah_agent, defa
assert first_file_agent.is_open is False, "First file should be closed"
@pytest.mark.asyncio
async def test_lru_no_eviction_when_reopening_same_file(server, default_user, sarah_agent, default_source):
"""Test that reopening an already open file doesn't trigger unnecessary eviction."""
import time
@@ -10442,7 +10373,6 @@ async def test_lru_no_eviction_when_reopening_same_file(server, default_user, sa
assert initial_open_names == final_open_names, "Same files should remain open"
@pytest.mark.asyncio
async def test_last_accessed_at_updates_correctly(server, default_user, sarah_agent, default_source):
"""Test that last_accessed_at is updated in the correct scenarios."""
import time
@@ -10493,7 +10423,6 @@ async def test_last_accessed_at_updates_correctly(server, default_user, sarah_ag
assert final_agent.last_accessed_at > prev_time, "mark_access should update timestamp"
@pytest.mark.asyncio
async def test_attach_files_bulk_basic(server, default_user, sarah_agent, default_source):
"""Test basic functionality of attach_files_bulk method."""
# Create multiple files
@@ -10538,7 +10467,6 @@ async def test_attach_files_bulk_basic(server, default_user, sarah_agent, defaul
assert attached_file.visible_content == f"visible content {i}"
@pytest.mark.asyncio
async def test_attach_files_bulk_deduplication(server, default_user, sarah_agent, default_source):
"""Test that attach_files_bulk properly deduplicates files with same names."""
# Create files with same name (different IDs)
@@ -10577,7 +10505,6 @@ async def test_attach_files_bulk_deduplication(server, default_user, sarah_agent
assert attached_files[0].file_name == "duplicate_test.txt"
@pytest.mark.asyncio
async def test_attach_files_bulk_lru_eviction(server, default_user, sarah_agent, default_source):
"""Test that attach_files_bulk properly handles LRU eviction without duplicates."""
import time
@@ -10657,7 +10584,6 @@ async def test_attach_files_bulk_lru_eviction(server, default_user, sarah_agent,
assert f"new_bulk_{i}.txt" in open_file_names
@pytest.mark.asyncio
async def test_attach_files_bulk_mixed_existing_new(server, default_user, sarah_agent, default_source):
"""Test bulk attach with mix of existing and new files."""
# Create and attach one file individually first
@@ -10723,7 +10649,6 @@ async def test_attach_files_bulk_mixed_existing_new(server, default_user, sarah_
assert existing_file_agent.visible_content == "updated content"
@pytest.mark.asyncio
async def test_attach_files_bulk_empty_list(server, default_user, sarah_agent):
"""Test attach_files_bulk with empty file list."""
closed_files = await server.file_agent_manager.attach_files_bulk(
@@ -10739,7 +10664,6 @@ async def test_attach_files_bulk_empty_list(server, default_user, sarah_agent):
assert len(attached_files) == 0
@pytest.mark.asyncio
async def test_attach_files_bulk_oversized_bulk(server, default_user, sarah_agent, default_source):
"""Test bulk attach when trying to attach more files than max_files_open allows."""
max_files_open = sarah_agent.max_files_open
@@ -10793,9 +10717,9 @@ async def test_attach_files_bulk_oversized_bulk(server, default_user, sarah_agen
FAILED tests/test_managers.py::test_high_concurrency_stress_test - AssertionError: High concurrency stress test failed with errors: [{'error': "(sqlalchemy.dialects.postgresql.asyncpg.Error) <class 'asyncpg.exceptions.DeadlockDetectedError'>: deadlock detected\nDETAIL: Process ***04 waits for ShareLock on transaction 30***3; blocked by process 84.\nProcess 84 waits for ShareLock on transaction 30***5; blocked by process ***04.\nHINT: See server log for query details.\n[SQL: INSERT INTO blocks_agents (agent_id, block_id, block_label) VALUES ($***::VARCHAR, $2::VARCHAR, $3::VARCHAR), ($4::VARCHAR, $5::VARCHAR, $6::VARCHAR), ($7::VARCHAR, $8::VARCHAR, $9::VARCHAR), ($***0::VARCHAR, $***::VARCHAR, $***2::VARCHAR) ON CONFLICT DO NOTHING]\n[parameters: ('agent-f69c0ffc-48ea-47f3-a6e0-e26a4***de764d', 'block-4506d355-b84a-44cd-bfdb-63a5039***07f***', 'stress_block_7', 'agent-f69c0ffc-48ea-47f3-a6e0-e26a4***de764d', 'block-cf32229c-9b43-4ed9-b65f-fc7cb***3567bf', 'stress_block_6', 'agent-f69c0ffc-48ea-47f3-a6e0-e26a4***de764d', 'block-02a***8***e7-44d6-402***-85a0-2c3dc20d9fae', 'stress_block_8', 'agent-f69c0ffc-48ea-47f3-a6e0-e26a4***de764d', 'block-4cba5***c***-42b8-4afa-aa59-97022c29f7a2', 'stress_block_0')]\n(Background on this error at: https://sqlalche.me/e/20/dbapi)", 'task_id': 4}]
"""
#
# @pytest.mark.asyncio
# @pytest.mark.asyncio(loop_scope="session")
# async def test_concurrent_block_updates_race_condition(
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser, event_loop
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser
# ):
# """Test that concurrent block updates don't cause race conditions."""
# agent, _ = comprehensive_test_agent_fixture
@@ -10847,9 +10771,9 @@ FAILED tests/test_managers.py::test_high_concurrency_stress_test - AssertionErro
# await server.block_manager.delete_block_async(block.id, actor=default_user)
#
#
# @pytest.mark.asyncio
# @pytest.mark.asyncio(loop_scope="session")
# async def test_concurrent_same_block_updates_race_condition(
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser, event_loop
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser
# ):
# """Test that multiple concurrent updates to the same block configuration don't cause issues."""
# agent, _ = comprehensive_test_agent_fixture
@@ -10885,9 +10809,9 @@ FAILED tests/test_managers.py::test_high_concurrency_stress_test - AssertionErro
# await server.block_manager.delete_block_async(block.id, actor=default_user)
#
#
# @pytest.mark.asyncio
# @pytest.mark.asyncio(loop_scope="session")
# async def test_concurrent_empty_block_updates_race_condition(
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser, event_loop
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser
# ):
# """Test concurrent updates that remove all blocks."""
# agent, _ = comprehensive_test_agent_fixture
@@ -10914,9 +10838,9 @@ FAILED tests/test_managers.py::test_high_concurrency_stress_test - AssertionErro
# assert len(final_agent.memory.blocks) == 0
#
#
# @pytest.mark.asyncio
# @pytest.mark.asyncio(loop_scope="session")
# async def test_concurrent_mixed_block_operations_race_condition(
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser, event_loop
# server: SyncServer, comprehensive_test_agent_fixture, default_user: PydanticUser
# ):
# """Test mixed concurrent operations: some adding blocks, some removing."""
# agent, _ = comprehensive_test_agent_fixture

View File

@@ -1,5 +1,3 @@
import os
import pytest
from letta.config import LettaConfig
@@ -15,41 +13,9 @@ from letta.schemas.group import (
SupervisorManager,
)
from letta.schemas.message import MessageCreate
from letta.server.db import db_registry
from letta.server.server import SyncServer
# Disable SQLAlchemy connection pooling for tests to prevent event loop issues
@pytest.fixture(scope="session", autouse=True)
def disable_db_pooling_for_tests():
"""Disable database connection pooling for the entire test session."""
os.environ["LETTA_DISABLE_SQLALCHEMY_POOLING"] = "true"
yield
# Clean up environment variable after tests
if "LETTA_DISABLE_SQLALCHEMY_POOLING" in os.environ:
del os.environ["LETTA_DISABLE_SQLALCHEMY_POOLING"]
@pytest.fixture(autouse=True)
async def cleanup_db_connections():
"""Cleanup database connections after each test."""
yield
# Dispose async engines in the current event loop
try:
if hasattr(db_registry, "_async_engines"):
for engine in db_registry._async_engines.values():
if engine:
await engine.dispose()
# Reset async initialization to force fresh connections
db_registry._initialized["async"] = False
db_registry._async_engines.clear()
db_registry._async_session_factories.clear()
except Exception as e:
# Log the error but don't fail the test
print(f"Warning: Failed to cleanup database connections: {e}")
@pytest.fixture(scope="module")
def server():
config = LettaConfig.load()
@@ -157,7 +123,6 @@ async def manager_agent(server, default_user):
yield agent_scooby
@pytest.mark.asyncio
async def test_empty_group(server, default_user):
group = await server.group_manager.create_group_async(
group=GroupCreate(
@@ -182,7 +147,6 @@ async def test_empty_group(server, default_user):
await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)
@pytest.mark.asyncio
async def test_modify_group_pattern(server, default_user, four_participant_agents, manager_agent):
group = await server.group_manager.create_group_async(
group=GroupCreate(
@@ -206,7 +170,6 @@ async def test_modify_group_pattern(server, default_user, four_participant_agent
await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)
@pytest.mark.asyncio
async def test_list_agent_groups(server, default_user, four_participant_agents):
group_a = await server.group_manager.create_group_async(
group=GroupCreate(
@@ -232,7 +195,6 @@ async def test_list_agent_groups(server, default_user, four_participant_agents):
await server.group_manager.delete_group_async(group_id=group_b.id, actor=default_user)
@pytest.mark.asyncio
async def test_round_robin(server, default_user, four_participant_agents):
description = (
"This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries."
@@ -344,7 +306,6 @@ async def test_round_robin(server, default_user, four_participant_agents):
await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)
@pytest.mark.asyncio
async def test_supervisor(server, default_user, four_participant_agents):
agent_scrappy = await server.create_agent_async(
request=CreateAgent(
@@ -408,7 +369,6 @@ async def test_supervisor(server, default_user, four_participant_agents):
server.agent_manager.delete_agent(agent_id=agent_scrappy.id, actor=default_user)
@pytest.mark.asyncio
@pytest.mark.flaky(max_runs=2)
async def test_dynamic_group_chat(server, default_user, manager_agent, four_participant_agents):
description = (

View File

@@ -198,6 +198,7 @@ async def test_vllm():
# assert embedding_models[0].handle == f"{provider.name}/{embedding_models[0].embedding_model}"
@pytest.mark.asyncio
async def test_custom_anthropic():
provider = AnthropicProvider(
name="custom_anthropic",

View File

@@ -23,6 +23,12 @@ from tests.helpers.utils import upload_file_and_wait
SERVER_PORT = 8283
def pytest_configure(config):
"""Override asyncio settings for this test file"""
# config.option.asyncio_default_fixture_loop_scope = "function"
config.option.asyncio_default_test_loop_scope = "function"
def run_server():
load_dotenv()

View File

@@ -1,4 +1,3 @@
import asyncio
import json
import os
import shutil
@@ -28,239 +27,6 @@ from letta.schemas.message import Message
from letta.server.server import SyncServer
from letta.system import unpack_message
WAR_AND_PEACE = """BOOK ONE: 1805
CHAPTER I
“Well, Prince, so Genoa and Lucca are now just family estates of the
Buonapartes. But I warn you, if you don't tell me that this means war,
if you still try to defend the infamies and horrors perpetrated by that
Antichrist—I really believe he is Antichrist—I will have nothing
more to do with you and you are no longer my friend, no longer my
'faithful slave,' as you call yourself! But how do you do? I see I
have frightened you—sit down and tell me all the news.”
It was in July, 1805, and the speaker was the well-known Anna Pávlovna
Schérer, maid of honor and favorite of the Empress Márya Fëdorovna.
With these words she greeted Prince Vasíli Kurágin, a man of high
rank and importance, who was the first to arrive at her reception. Anna
Pávlovna had had a cough for some days. She was, as she said, suffering
from la grippe; grippe being then a new word in St. Petersburg, used
only by the elite.
All her invitations without exception, written in French, and delivered
by a scarlet-liveried footman that morning, ran as follows:
“If you have nothing better to do, Count (or Prince), and if the
prospect of spending an evening with a poor invalid is not too terrible,
I shall be very charmed to see you tonight between 7 and 10—Annette
Schérer.”
“Heavens! what a virulent attack!” replied the prince, not in the
least disconcerted by this reception. He had just entered, wearing an
embroidered court uniform, knee breeches, and shoes, and had stars on
his breast and a serene expression on his flat face. He spoke in that
refined French in which our grandfathers not only spoke but thought, and
with the gentle, patronizing intonation natural to a man of importance
who had grown old in society and at court. He went up to Anna Pávlovna,
kissed her hand, presenting to her his bald, scented, and shining head,
and complacently seated himself on the sofa.
“First of all, dear friend, tell me how you are. Set your friend's
mind at rest,” said he without altering his tone, beneath the
politeness and affected sympathy of which indifference and even irony
could be discerned.
“Can one be well while suffering morally? Can one be calm in times
like these if one has any feeling?” said Anna Pávlovna. “You are
staying the whole evening, I hope?”
“And the fete at the English ambassador's? Today is Wednesday. I
must put in an appearance there,” said the prince. “My daughter is
coming for me to take me there.”
“I thought today's fete had been canceled. I confess all these
festivities and fireworks are becoming wearisome.”
“If they had known that you wished it, the entertainment would have
been put off,” said the prince, who, like a wound-up clock, by force
of habit said things he did not even wish to be believed.
“Don't tease! Well, and what has been decided about Novosíltsev's
dispatch? You know everything.”
“What can one say about it?” replied the prince in a cold, listless
tone. “What has been decided? They have decided that Buonaparte has
burnt his boats, and I believe that we are ready to burn ours.”
Prince Vasíli always spoke languidly, like an actor repeating a stale
part. Anna Pávlovna Schérer on the contrary, despite her forty years,
overflowed with animation and impulsiveness. To be an enthusiast had
become her social vocation and, sometimes even when she did not
feel like it, she became enthusiastic in order not to disappoint the
expectations of those who knew her. The subdued smile which, though it
did not suit her faded features, always played round her lips expressed,
as in a spoiled child, a continual consciousness of her charming defect,
which she neither wished, nor could, nor considered it necessary, to
correct.
In the midst of a conversation on political matters Anna Pávlovna burst
out:
“Oh, don't speak to me of Austria. Perhaps I don't understand
things, but Austria never has wished, and does not wish, for war. She
is betraying us! Russia alone must save Europe. Our gracious sovereign
recognizes his high vocation and will be true to it. That is the one
thing I have faith in! Our good and wonderful sovereign has to perform
the noblest role on earth, and he is so virtuous and noble that God will
not forsake him. He will fulfill his vocation and crush the hydra of
revolution, which has become more terrible than ever in the person of
this murderer and villain! We alone must avenge the blood of the just
one.... Whom, I ask you, can we rely on?... England with her commercial
spirit will not and cannot understand the Emperor Alexander's
loftiness of soul. She has refused to evacuate Malta. She wanted to
find, and still seeks, some secret motive in our actions. What answer
did Novosíltsev get? None. The English have not understood and cannot
understand the self-abnegation of our Emperor who wants nothing for
himself, but only desires the good of mankind. And what have they
promised? Nothing! And what little they have promised they will not
perform! Prussia has always declared that Buonaparte is invincible, and
that all Europe is powerless before him.... And I don't believe a
word that Hardenburg says, or Haugwitz either. This famous Prussian
neutrality is just a trap. I have faith only in God and the lofty
destiny of our adored monarch. He will save Europe!”
She suddenly paused, smiling at her own impetuosity.
“I think,” said the prince with a smile, “that if you had been
sent instead of our dear Wintzingerode you would have captured the King
of Prussia's consent by assault. You are so eloquent. Will you give me
a cup of tea?”
“In a moment. À propos,” she added, becoming calm again, “I am
expecting two very interesting men tonight, le Vicomte de Mortemart, who
is connected with the Montmorencys through the Rohans, one of the best
French families. He is one of the genuine émigrés, the good ones. And
also the Abbé Morio. Do you know that profound thinker? He has been
received by the Emperor. Had you heard?”
“I shall be delighted to meet them,” said the prince. “But
tell me,” he added with studied carelessness as if it had only just
occurred to him, though the question he was about to ask was the chief
motive of his visit, “is it true that the Dowager Empress wants
Baron Funke to be appointed first secretary at Vienna? The baron by all
accounts is a poor creature.”
Prince Vasíli wished to obtain this post for his son, but others were
trying through the Dowager Empress Márya Fëdorovna to secure it for
the baron.
Anna Pávlovna almost closed her eyes to indicate that neither she nor
anyone else had a right to criticize what the Empress desired or was
pleased with.
“Baron Funke has been recommended to the Dowager Empress by her
sister,” was all she said, in a dry and mournful tone.
As she named the Empress, Anna Pávlovna's face suddenly assumed an
expression of profound and sincere devotion and respect mingled with
sadness, and this occurred every time she mentioned her illustrious
patroness. She added that Her Majesty had deigned to show Baron Funke
beaucoup d'estime, and again her face clouded over with sadness.
The prince was silent and looked indifferent. But, with the womanly and
courtierlike quickness and tact habitual to her, Anna Pávlovna
wished both to rebuke him (for daring to speak as he had done of a man
recommended to the Empress) and at the same time to console him, so she
said:
“Now about your family. Do you know that since your daughter came
out everyone has been enraptured by her? They say she is amazingly
beautiful.”
The prince bowed to signify his respect and gratitude.
“I often think,” she continued after a short pause, drawing nearer
to the prince and smiling amiably at him as if to show that political
and social topics were ended and the time had come for intimate
conversation—“I often think how unfairly sometimes the joys of life
are distributed. Why has fate given you two such splendid children?
I don't speak of Anatole, your youngest. I don't like him,” she
added in a tone admitting of no rejoinder and raising her eyebrows.
“Two such charming children. And really you appreciate them less than
anyone, and so you don't deserve to have them.”
And she smiled her ecstatic smile.
“I can't help it,” said the prince. “Lavater would have said I
lack the bump of paternity.”
“Don't joke; I mean to have a serious talk with you. Do you know
I am dissatisfied with your younger son? Between ourselves” (and her
face assumed its melancholy expression), “he was mentioned at Her
Majesty's and you were pitied....”
The prince answered nothing, but she looked at him significantly,
awaiting a reply. He frowned.
“What would you have me do?” he said at last. “You know I did all
a father could for their education, and they have both turned out fools.
Hippolyte is at least a quiet fool, but Anatole is an active one. That
is the only difference between them.” He said this smiling in a way
more natural and animated than usual, so that the wrinkles round
his mouth very clearly revealed something unexpectedly coarse and
unpleasant.
“And why are children born to such men as you? If you were not a
father there would be nothing I could reproach you with,” said Anna
Pávlovna, looking up pensively.
“I am your faithful slave and to you alone I can confess that my
children are the bane of my life. It is the cross I have to bear. That
is how I explain it to myself. It can't be helped!”
He said no more, but expressed his resignation to cruel fate by a
gesture. Anna Pávlovna meditated.
“Have you never thought of marrying your prodigal son Anatole?” she
asked. “They say old maids have a mania for matchmaking, and though I
don't feel that weakness in myself as yet, I know a little person who
is very unhappy with her father. She is a relation of yours, Princess
Mary Bolkónskaya.”
Prince Vasíli did not reply, though, with the quickness of memory and
perception befitting a man of the world, he indicated by a movement of
the head that he was considering this information.
“Do you know,” he said at last, evidently unable to check the sad
current of his thoughts, “that Anatole is costing me forty thousand
rubles a year? And,” he went on after a pause, “what will it be in
five years, if he goes on like this?” Presently he added: “That's
what we fathers have to put up with.... Is this princess of yours
rich?”
“Her father is very rich and stingy. He lives in the country. He is
the well-known Prince Bolkónski who had to retire from the army under
the late Emperor, and was nicknamed 'the King of Prussia.' He is
very clever but eccentric, and a bore. The poor girl is very unhappy.
She has a brother; I think you know him, he married Lise Meinen lately.
He is an aide-de-camp of Kutúzov's and will be here tonight.”
“Listen, dear Annette,” said the prince, suddenly taking Anna
Pávlovna's hand and for some reason drawing it downwards. “Arrange
that affair for me and I shall always be your most devoted slave-slafe
with an f, as a village elder of mine writes in his reports. She is rich
and of good family and that's all I want.”
And with the familiarity and easy grace peculiar to him, he raised the
maid of honor's hand to his lips, kissed it, and swung it to and fro
as he lay back in his armchair, looking in another direction.
“Attendez,” said Anna Pávlovna, reflecting, “I'll speak to
Lise, young Bolkónski's wife, this very evening, and perhaps the
thing can be arranged. It shall be on your family's behalf that I'll
start my apprenticeship as old maid."""
@pytest.fixture(scope="module")
def server():
@@ -359,14 +125,6 @@ def other_agent_id(server, user_id, base_tools):
server.agent_manager.delete_agent(agent_state.id, actor=actor)
@pytest.fixture(scope="session")
def event_loop(request):
"""Create an instance of the default event loop for each test case."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
def test_error_on_nonexistent_agent(server, user, agent_id):
try:
fake_agent_id = str(uuid.uuid4())
@@ -456,18 +214,21 @@ async def test_get_context_window_overview(server: SyncServer, user, agent_id):
assert overview.messages is not None
assert overview.context_window_size_max >= overview.context_window_size_current
assert overview.context_window_size_current == (
overview.num_tokens_system
+ overview.num_tokens_core_memory
+ overview.num_tokens_summary_memory
+ overview.num_tokens_messages
+ overview.num_tokens_functions_definitions
+ overview.num_tokens_external_memory_summary
assert overview.context_window_size_current == sum(
(
overview.num_tokens_system,
overview.num_tokens_core_memory,
overview.num_tokens_summary_memory,
overview.num_tokens_messages,
overview.num_tokens_functions_definitions,
overview.num_tokens_external_memory_summary,
)
)
def test_delete_agent_same_org(server: SyncServer, org_id: str, user: User):
agent_state = server.create_agent(
@pytest.mark.asyncio(loop_scope="session")
async def test_delete_agent_same_org(server: SyncServer, org_id: str, user: User):
agent_state = await server.create_agent_async(
request=CreateAgent(
name="nonexistent_tools_agent",
memory_blocks=[],
@@ -478,10 +239,10 @@ def test_delete_agent_same_org(server: SyncServer, org_id: str, user: User):
)
# create another user in the same org
another_user = server.user_manager.create_user(User(organization_id=org_id, name="another"))
another_user = await server.user_manager.create_actor_async(User(organization_id=org_id, name="another"))
# test that another user in the same org can delete the agent
server.agent_manager.delete_agent(agent_state.id, actor=another_user)
await server.agent_manager.delete_agent_async(agent_state.id, actor=another_user)
@pytest.mark.asyncio
@@ -720,7 +481,7 @@ def ingest(message: str):
import pytest
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_tool_run_basic(server, disable_e2b_api_key, user):
"""Test running a simple tool from source"""
result = await server.run_tool_from_source(
@@ -735,7 +496,7 @@ async def test_tool_run_basic(server, disable_e2b_api_key, user):
assert not result.stderr
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_tool_run_with_env_var(server, disable_e2b_api_key, user):
"""Test running a tool that uses an environment variable"""
result = await server.run_tool_from_source(
@@ -751,7 +512,7 @@ async def test_tool_run_with_env_var(server, disable_e2b_api_key, user):
assert not result.stderr
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_tool_run_invalid_args(server, disable_e2b_api_key, user):
"""Test running a tool with incorrect arguments"""
result = await server.run_tool_from_source(
@@ -768,7 +529,7 @@ async def test_tool_run_invalid_args(server, disable_e2b_api_key, user):
assert "missing 1 required positional argument" in result.stderr[0]
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_tool_run_with_distractor(server, disable_e2b_api_key, user):
"""Test running a tool with a distractor function in the source"""
result = await server.run_tool_from_source(
@@ -784,7 +545,7 @@ async def test_tool_run_with_distractor(server, disable_e2b_api_key, user):
assert not result.stderr
@pytest.mark.asyncio
@pytest.mark.asyncio(scope="session")
async def test_tool_run_explicit_tool_name(server, disable_e2b_api_key, user):
"""Test selecting a tool by name when multiple tools exist in the source"""
result = await server.run_tool_from_source(
@@ -801,7 +562,7 @@ async def test_tool_run_explicit_tool_name(server, disable_e2b_api_key, user):
assert not result.stderr
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_tool_run_util_function(server, disable_e2b_api_key, user):
"""Test selecting a utility function that does not return anything meaningful"""
result = await server.run_tool_from_source(
@@ -818,7 +579,7 @@ async def test_tool_run_util_function(server, disable_e2b_api_key, user):
assert not result.stderr
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_tool_run_with_explicit_json_schema(server, disable_e2b_api_key, user):
"""Test overriding the autogenerated JSON schema with an explicit one"""
explicit_json_schema = {
@@ -941,13 +702,13 @@ def test_default_tool_rules(server: SyncServer, user_id: str, base_tools, base_m
assert len(agent_state.tool_rules) == len(base_tools + base_memory_tools)
@pytest.mark.asyncio
@pytest.mark.asyncio(loop_scope="session")
async def test_add_remove_tools_update_agent(server: SyncServer, user_id: str, base_tools, base_memory_tools):
"""Test that the memory rebuild is generating the correct number of role=system messages"""
actor = server.user_manager.get_user_or_default(user_id)
# create agent
agent_state = server.create_agent(
agent_state = await server.create_agent_async(
request=CreateAgent(
name="memory_rebuild_test_agent",
tool_ids=[],

531
uv.lock generated
View File

@@ -239,6 +239,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
]
[[package]]
name = "appnope"
version = "0.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
]
[[package]]
name = "apscheduler"
version = "3.11.0"
@@ -713,6 +722,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
]
[[package]]
name = "comm"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" },
]
[[package]]
name = "composio-core"
version = "0.7.20"
@@ -900,6 +918,27 @@ http = [
{ name = "httpx" },
]
[[package]]
name = "debugpy"
version = "1.8.16"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/d4/722d0bcc7986172ac2ef3c979ad56a1030e3afd44ced136d45f8142b1f4a/debugpy-1.8.16.tar.gz", hash = "sha256:31e69a1feb1cf6b51efbed3f6c9b0ef03bc46ff050679c4be7ea6d2e23540870", size = 1643809, upload-time = "2025-08-06T18:00:02.647Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/63/d6/ad70ba8b49b23fa286fb21081cf732232cc19374af362051da9c7537ae52/debugpy-1.8.16-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67371b28b79a6a12bcc027d94a06158f2fde223e35b5c4e0783b6f9d3b39274a", size = 2184063, upload-time = "2025-08-06T18:00:11.885Z" },
{ url = "https://files.pythonhosted.org/packages/aa/49/7b03e88dea9759a4c7910143f87f92beb494daaae25560184ff4ae883f9e/debugpy-1.8.16-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2abae6dd02523bec2dee16bd6b0781cccb53fd4995e5c71cc659b5f45581898", size = 3134837, upload-time = "2025-08-06T18:00:13.782Z" },
{ url = "https://files.pythonhosted.org/packages/5d/52/b348930316921de7565fbe37a487d15409041713004f3d74d03eb077dbd4/debugpy-1.8.16-cp311-cp311-win32.whl", hash = "sha256:f8340a3ac2ed4f5da59e064aa92e39edd52729a88fbde7bbaa54e08249a04493", size = 5159142, upload-time = "2025-08-06T18:00:15.391Z" },
{ url = "https://files.pythonhosted.org/packages/d8/ef/9aa9549ce1e10cea696d980292e71672a91ee4a6a691ce5f8629e8f48c49/debugpy-1.8.16-cp311-cp311-win_amd64.whl", hash = "sha256:70f5fcd6d4d0c150a878d2aa37391c52de788c3dc680b97bdb5e529cb80df87a", size = 5183117, upload-time = "2025-08-06T18:00:17.251Z" },
{ url = "https://files.pythonhosted.org/packages/61/fb/0387c0e108d842c902801bc65ccc53e5b91d8c169702a9bbf4f7efcedf0c/debugpy-1.8.16-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b202e2843e32e80b3b584bcebfe0e65e0392920dc70df11b2bfe1afcb7a085e4", size = 2511822, upload-time = "2025-08-06T18:00:18.526Z" },
{ url = "https://files.pythonhosted.org/packages/37/44/19e02745cae22bf96440141f94e15a69a1afaa3a64ddfc38004668fcdebf/debugpy-1.8.16-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64473c4a306ba11a99fe0bb14622ba4fbd943eb004847d9b69b107bde45aa9ea", size = 4230135, upload-time = "2025-08-06T18:00:19.997Z" },
{ url = "https://files.pythonhosted.org/packages/f3/0b/19b1ba5ee4412f303475a2c7ad5858efb99c90eae5ec627aa6275c439957/debugpy-1.8.16-cp312-cp312-win32.whl", hash = "sha256:833a61ed446426e38b0dd8be3e9d45ae285d424f5bf6cd5b2b559c8f12305508", size = 5281271, upload-time = "2025-08-06T18:00:21.281Z" },
{ url = "https://files.pythonhosted.org/packages/b1/e0/bc62e2dc141de53bd03e2c7cb9d7011de2e65e8bdcdaa26703e4d28656ba/debugpy-1.8.16-cp312-cp312-win_amd64.whl", hash = "sha256:75f204684581e9ef3dc2f67687c3c8c183fde2d6675ab131d94084baf8084121", size = 5323149, upload-time = "2025-08-06T18:00:23.033Z" },
{ url = "https://files.pythonhosted.org/packages/62/66/607ab45cc79e60624df386e233ab64a6d8d39ea02e7f80e19c1d451345bb/debugpy-1.8.16-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:85df3adb1de5258dca910ae0bb185e48c98801ec15018a263a92bb06be1c8787", size = 2496157, upload-time = "2025-08-06T18:00:24.361Z" },
{ url = "https://files.pythonhosted.org/packages/4d/a0/c95baae08a75bceabb79868d663a0736655e427ab9c81fb848da29edaeac/debugpy-1.8.16-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee89e948bc236a5c43c4214ac62d28b29388453f5fd328d739035e205365f0b", size = 4222491, upload-time = "2025-08-06T18:00:25.806Z" },
{ url = "https://files.pythonhosted.org/packages/5b/2f/1c8db6ddd8a257c3cd2c46413b267f1d5fa3df910401c899513ce30392d6/debugpy-1.8.16-cp313-cp313-win32.whl", hash = "sha256:cf358066650439847ec5ff3dae1da98b5461ea5da0173d93d5e10f477c94609a", size = 5281126, upload-time = "2025-08-06T18:00:27.207Z" },
{ url = "https://files.pythonhosted.org/packages/d3/ba/c3e154ab307366d6c5a9c1b68de04914e2ce7fa2f50d578311d8cc5074b2/debugpy-1.8.16-cp313-cp313-win_amd64.whl", hash = "sha256:b5aea1083f6f50023e8509399d7dc6535a351cc9f2e8827d1e093175e4d9fa4c", size = 5323094, upload-time = "2025-08-06T18:00:29.03Z" },
{ url = "https://files.pythonhosted.org/packages/52/57/ecc9ae29fa5b2d90107cd1d9bf8ed19aacb74b2264d986ae9d44fe9bdf87/debugpy-1.8.16-py2.py3-none-any.whl", hash = "sha256:19c9521962475b87da6f673514f7fd610328757ec993bf7ec0d8c96f9a325f9e", size = 5287700, upload-time = "2025-08-06T18:00:42.333Z" },
]
[[package]]
name = "decorator"
version = "5.2.1"
@@ -988,7 +1027,7 @@ wheels = [
[[package]]
name = "e2b"
version = "1.11.1"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -999,23 +1038,23 @@ dependencies = [
{ name = "python-dateutil" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b4/49/2110d31074da3ba233984c6c02fa5def9ab043669cdb921f380b71457329/e2b-1.11.1.tar.gz", hash = "sha256:7f7b6f238208d0a23353bb0da01f91a924321b57c61b176506862cbc1493ce8c", size = 61926, upload-time = "2025-08-06T10:31:42.747Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/61/4cacf82b1d00f888678fdcfff23a8c3a09aed25b2993eea7af472e20dda5/e2b-2.0.0.tar.gz", hash = "sha256:4d033d937b0a09b8428e73233321a913cbaef8e7299fc731579c656e9d53a144", size = 66401, upload-time = "2025-08-21T15:50:40.207Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/a4/be83c8b1cd923b558fb76a0fcbc4172b5348c956c706c70e19c038bc37f8/e2b-1.11.1-py3-none-any.whl", hash = "sha256:1ecb123873788472731c101939a494ab852cbcce0f913df6f7ecb194ae932130", size = 114762, upload-time = "2025-08-06T10:31:40.971Z" },
{ url = "https://files.pythonhosted.org/packages/92/76/ddf676a327006b9c3bdec1a10b99aad4f10af8b2cbc3358ebb156951914e/e2b-2.0.0-py3-none-any.whl", hash = "sha256:a6621b905cb2a883a9c520736ae98343a6184fc90c29b4f2f079d720294a0df0", size = 123785, upload-time = "2025-08-21T15:50:38.579Z" },
]
[[package]]
name = "e2b-code-interpreter"
version = "1.5.2"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "e2b" },
{ name = "httpx" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/85/b4a1c9427b45818d4c3773ed967ec1fcc2d7b677e096d8051303889adc2d/e2b_code_interpreter-1.5.2.tar.gz", hash = "sha256:3bd6ea70596290e85aaf0a2f19f28bf37a5e73d13086f5e6a0080bb591c5a547", size = 10006, upload-time = "2025-07-07T14:58:28.676Z" }
sdist = { url = "https://files.pythonhosted.org/packages/42/84/e94cb706d88ac3510748be9d0030fb0a896a25b43677903aff20213b3cc1/e2b_code_interpreter-2.0.0.tar.gz", hash = "sha256:19136916be8de60bfd0a678742501d1d0335442bb6e86405c7dd6f98059b73c4", size = 10029, upload-time = "2025-08-22T10:16:57.169Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f5/4a/7dc5c673c47418e1b38594ab3b022ee20ea1bf3ff8f8aa8273d6ddc99532/e2b_code_interpreter-1.5.2-py3-none-any.whl", hash = "sha256:5c3188d8f25226b28fef4b255447cc6a4c36afb748bdd5180b45be486d5169f3", size = 12873, upload-time = "2025-07-07T14:58:27.622Z" },
{ url = "https://files.pythonhosted.org/packages/5e/f1/135acbaffe4b2e63addecfc2a6c2ecf9ea3e5394aa2a9a829e3eb6f2098d/e2b_code_interpreter-2.0.0-py3-none-any.whl", hash = "sha256:273642d4dd78f09327fb1553fe4f7ddcf17892b78f98236e038d29985e42dca5", size = 12939, upload-time = "2025-08-22T10:16:55.698Z" },
]
[[package]]
@@ -1038,14 +1077,14 @@ wheels = [
[[package]]
name = "faker"
version = "37.5.3"
version = "37.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/5d/7797a74e8e31fa227f0303239802c5f09b6722bdb6638359e7b6c8f30004/faker-37.5.3.tar.gz", hash = "sha256:8315d8ff4d6f4f588bd42ffe63abd599886c785073e26a44707e10eeba5713dc", size = 1907147, upload-time = "2025-07-30T15:52:19.528Z" }
sdist = { url = "https://files.pythonhosted.org/packages/24/cd/f7679c20f07d9e2013123b7f7e13809a3450a18d938d58e86081a486ea15/faker-37.6.0.tar.gz", hash = "sha256:0f8cc34f30095184adf87c3c24c45b38b33ad81c35ef6eb0a3118f301143012c", size = 1907960, upload-time = "2025-08-26T15:56:27.419Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/bf/d06dd96e7afa72069dbdd26ed0853b5e8bd7941e2c0819a9b21d6e6fc052/faker-37.5.3-py3-none-any.whl", hash = "sha256:386fe9d5e6132a915984bf887fcebcc72d6366a25dd5952905b31b141a17016d", size = 1949261, upload-time = "2025-07-30T15:52:17.729Z" },
{ url = "https://files.pythonhosted.org/packages/61/7d/8b50e4ac772719777be33661f4bde320793400a706f5eb214e4de46f093c/faker-37.6.0-py3-none-any.whl", hash = "sha256:3c5209b23d7049d596a51db5d76403a0ccfea6fc294ffa2ecfef6a8843b1e6a7", size = 1949837, upload-time = "2025-08-26T15:56:25.33Z" },
]
[[package]]
@@ -1488,65 +1527,68 @@ wheels = [
[[package]]
name = "granian"
version = "2.5.0"
version = "2.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e7/91/6b51c5749a58e5d86063b193c15914700464f0d64eda84178bf432dbbcf9/granian-2.5.0.tar.gz", hash = "sha256:bed0d047c9c0c6c6a5a85ee5b3c7e2683fc63e03ac032eaf3d7654fa96bde102", size = 110336, upload-time = "2025-07-30T18:55:15.161Z" }
sdist = { url = "https://files.pythonhosted.org/packages/eb/18/11085d3d7c97ff93501ae6acc8217943d9e8f2ac45d9baf51ab146a2355b/granian-2.5.1.tar.gz", hash = "sha256:c268be38053bd29351bf8f86d87a5862708033ee5322ac47465b99ff45670783", size = 111858, upload-time = "2025-08-26T16:11:09.204Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/f3/7d9cee103d91f4d1b934ebaa0cea944638a7b4940c5af72163e486cd4989/granian-2.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0eda4c389c222aa5455b7205640df0207201a86c46e5be98dd0040b6cc45146a", size = 3044837, upload-time = "2025-07-30T18:52:49.893Z" },
{ url = "https://files.pythonhosted.org/packages/ef/b8/fcb93a7bddcedc0af11a446094b33dc99af93a338abd8e95747aae3d1112/granian-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58aea28ebb2cdf7545ee3cb1c8593c8b2f857a9fa6219589ecd3f5a4b365262f", size = 2602770, upload-time = "2025-07-30T18:52:51.588Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d9/5f94af3cbdbe023774d24616648e428cfd307f18232a64d82caa2ad8113a/granian-2.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf9dc480d481ae834a085f1f46213ebf80512b1ace0559f6c0335edb24be0e92", size = 3347657, upload-time = "2025-07-30T18:52:53.694Z" },
{ url = "https://files.pythonhosted.org/packages/82/12/ba9be1b7a9ad28b66735a648a996578b64873c580a3a0681575e60cfa0f8/granian-2.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:604c544273b36091b54fdf66d4e9a0f98dc0369b380ba5dd328478ba65cda320", size = 2949970, upload-time = "2025-07-30T18:52:55.359Z" },
{ url = "https://files.pythonhosted.org/packages/07/54/f29100152e7dd6f5dfdff2626b040711735aff2ec9f61cba8e7d04614a5e/granian-2.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6d70f6e7edd183afb62468a7fc175348145aec303297a41b1714a9b6d8150d", size = 3233777, upload-time = "2025-07-30T18:52:57.117Z" },
{ url = "https://files.pythonhosted.org/packages/04/e0/988993586ba3d5e80cc87fc464601df55634ec440eb10889c8fdd3b613ac/granian-2.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:651cbad3a137b762885b7e57dea77b5d11262b0a2c16d4b61b4812bc8bc5ffa4", size = 3108386, upload-time = "2025-07-30T18:52:58.644Z" },
{ url = "https://files.pythonhosted.org/packages/6a/06/815fde5195f40a2a1be4e78ad0c3cdd05727dcca59a5a231d0df95fb6f68/granian-2.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:22bac6aace54e4183831a46a4033c076100150527676e666eeb84df9829e0e1c", size = 3114733, upload-time = "2025-07-30T18:53:00.059Z" },
{ url = "https://files.pythonhosted.org/packages/fb/7f/a8d2fce7f810aa3b7b16550cfbde4756eeb3efcd3646b0adb6c6b7d4bf95/granian-2.5.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:0429dd0c4c21c123b06b7f9057ecb4c1fc3d6228f442276e27545a5ad6fc780c", size = 3495857, upload-time = "2025-07-30T18:53:01.43Z" },
{ url = "https://files.pythonhosted.org/packages/29/58/984b53efc3b245f5f9b8d76822061b3fb4c5c1024d526ffe59da55b5405c/granian-2.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6141582757eb2bb8c8390637a3ac29dbba0c10db93192adb360c7060f149782", size = 3273079, upload-time = "2025-07-30T18:53:02.792Z" },
{ url = "https://files.pythonhosted.org/packages/d5/08/047b3004ebbad8934b4b963c1ab5a0cd449c94c68f95557d57a7d561695d/granian-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:29da6a1c6dfc728fc96649b514e26b38848e40e4e78676f51f2ed90c51da733e", size = 2331232, upload-time = "2025-07-30T18:53:04.174Z" },
{ url = "https://files.pythonhosted.org/packages/d1/31/590b932524f43289aa9f735d0b92ccdd97b2d9e388a5acad171fc01382e4/granian-2.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e7627c3b7e3f9c024c4edd80636e8326fbce0420889e0951da349d13742e503", size = 3026668, upload-time = "2025-07-30T18:53:05.505Z" },
{ url = "https://files.pythonhosted.org/packages/50/94/6bcd3d0cec40994112dfd2b3102f4ff3bd2e62928f6524fb95f38fae6647/granian-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0a4c317f30c227baf16f541f0c93cb08ee45fbf8a2ef5317ba07b6bd6b7c877", size = 2584723, upload-time = "2025-07-30T18:53:06.865Z" },
{ url = "https://files.pythonhosted.org/packages/2c/5b/44089c2384ba2e3e5a3cdf08e8a000bff07cf7382fa2d9a0e4e1a9ba6451/granian-2.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35eb58b0f80fc9f55d5210d339ba8f5f8d9c126a2e29f051e8b62353e3f84b1d", size = 3326781, upload-time = "2025-07-30T18:53:08.323Z" },
{ url = "https://files.pythonhosted.org/packages/bf/7d/7ca304dde1ce475b83e4add007e36a87284e6838f158db87c11a2c93f379/granian-2.5.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def250f04c0374069278152bf3e08ecb1f67e0c99d3eb14d902df1e1558b93fa", size = 2937454, upload-time = "2025-07-30T18:53:10.099Z" },
{ url = "https://files.pythonhosted.org/packages/17/6c/858a7cce6ce07adc2f0e7b1809af61d1af2affbc07edaecd127f16207b37/granian-2.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eda01a9027f2921f42b4f8bb16e46f8ab67a5345e52b3ffeedd2f921a09c87b6", size = 3229723, upload-time = "2025-07-30T18:53:11.464Z" },
{ url = "https://files.pythonhosted.org/packages/8a/7b/ded645443ec95921d407e3d277418987a4aed955830f67d6b151c8c095f9/granian-2.5.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c8a51bcb7e533ab75d2d9e13432e9b63c90eeb7fdde700875253efbfb2dcdcbc", size = 3109804, upload-time = "2025-07-30T18:53:13.179Z" },
{ url = "https://files.pythonhosted.org/packages/0a/64/837131cd49e17c219e2a04ed711459e0f93bd5c1db7fc243666e1b9d412a/granian-2.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a9c5670171a97c35aeea79fd7100058e461b0271a7e81fdecd586c770cdd2b41", size = 3099711, upload-time = "2025-07-30T18:53:14.764Z" },
{ url = "https://files.pythonhosted.org/packages/8a/4e/80578d06426a40a2718b3183c5e32ba570001153cbbb3fe523d3f9e89880/granian-2.5.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:760275d286142775fb21c85d96fc4996e00de9d3b054c424b2c8519679aa14b5", size = 3468897, upload-time = "2025-07-30T18:53:16.477Z" },
{ url = "https://files.pythonhosted.org/packages/49/8c/7e5d0187a4e53830cc49231a55f01d3d251eec0cbb09209a0ffde8b33741/granian-2.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1573755288d70f37b55acb14f01cb3a7f7cdca7bf143f908c649ada254e9cb6", size = 3275035, upload-time = "2025-07-30T18:53:17.947Z" },
{ url = "https://files.pythonhosted.org/packages/f5/5c/3176f48ef4c723a0bd5143b59c598957749d05a4f88db5e03760858deae7/granian-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f60c46cf8684a6b5c1d9bf88cc9a248682a753874e14bd2cc6c81c2001449dab", size = 2321533, upload-time = "2025-07-30T18:53:19.589Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d8/c3c8a452f1b590400bb2cdef1ca61da8e9913762884cf3e6ba801fd3fdad/granian-2.5.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:50d4dc74ab763c1bf396cf85d93a8202bf1bfb74150b03f9fd62b600cd0c777c", size = 3026123, upload-time = "2025-07-30T18:53:20.94Z" },
{ url = "https://files.pythonhosted.org/packages/89/f0/e7038189d4e3b5f1e10bc23547b687bcdecdefbddef87013db64efea6800/granian-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:31705782cd616b9b70536c1b61b7f15815ebc4dcccdb72f58aa806ba7ac5dfa1", size = 2584469, upload-time = "2025-07-30T18:53:22.579Z" },
{ url = "https://files.pythonhosted.org/packages/6a/2d/718620f393b6030d4a9ac5d1bc66cc5d159fb7f7c60c5d4483fd43902f7d/granian-2.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bbc4ebc727202ad4b3073ca8148c2af49904710d6fce84872191b2dd5cd36916", size = 3326593, upload-time = "2025-07-30T18:53:24.288Z" },
{ url = "https://files.pythonhosted.org/packages/07/57/c8b5f014673717e850db6d551057e74e330aadad5250d3f312cee432b1ec/granian-2.5.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af272218076663280fdc293b7da3adb716f23d54211cefad92fcf7e01b3eed19", size = 2937464, upload-time = "2025-07-30T18:53:25.72Z" },
{ url = "https://files.pythonhosted.org/packages/fe/91/8ae8aa9f0c3bbdf42fada15d623a5ab9fffd38acc243ec5619b6cbd60b9a/granian-2.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36493c4f2b672d027eb11b05ca6660f9fd4944452841d213cb0cb64da869539b", size = 3229316, upload-time = "2025-07-30T18:53:27.262Z" },
{ url = "https://files.pythonhosted.org/packages/a5/8c/6c294cf3d77dc9524530d30057f9b6e334cc12c5414feb604fb277d030a3/granian-2.5.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:afafac4908d5931e4b2c2a09612e063d7ccd05e531f16b7f11e3bccc4ca8972c", size = 3109818, upload-time = "2025-07-30T18:53:29.004Z" },
{ url = "https://files.pythonhosted.org/packages/4a/49/acc3f1e02e35009d9486e4e00d2c951798a8098935d2374f52c7d2728438/granian-2.5.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:fb157c3d66301ffad4113da4c51aed4d56006b9ebe9d0892c682a634b5fff773", size = 3099384, upload-time = "2025-07-30T18:53:30.448Z" },
{ url = "https://files.pythonhosted.org/packages/c1/87/7cdd96fbeabbceea3820736e65bd6d8c0021983605cee26ef1bf2e11e24b/granian-2.5.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:879fdeb71fe279175a25d709d95dd2db01eb67cd12d300e51e3dc704ca5e52fd", size = 3468575, upload-time = "2025-07-30T18:53:31.857Z" },
{ url = "https://files.pythonhosted.org/packages/fd/64/bd41efc6bfbca0ff871ce28a13b9e687055dd70913dfce92c4a21a264bf7/granian-2.5.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:74601bda3aedb249a3d5059d48108acfa61d6f71686162bda0bedc013a443efb", size = 3274703, upload-time = "2025-07-30T18:53:33.378Z" },
{ url = "https://files.pythonhosted.org/packages/97/42/c1a8f51d7ce3408a6aeebf68338e0282f0123b65c2fef1d7c202a407062d/granian-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:76dc084d1c528683a88c2d1a00786c9bc013b695b1776ad8a3c71419c45e1df0", size = 2321042, upload-time = "2025-07-30T18:53:34.82Z" },
{ url = "https://files.pythonhosted.org/packages/e9/43/af71556ea889c28b8c1c74e9f50a64c040a92bae5e4412b8617638a8aa0e/granian-2.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f371dd9eedae26158901fee3eb934e8fa61491cc78d234470ce364b989c78a1f", size = 2955162, upload-time = "2025-07-30T18:53:36.263Z" },
{ url = "https://files.pythonhosted.org/packages/c2/35/14c2c050f3df95eb054f2a44b41a02c30c8a04dc8cca888330f55c43a436/granian-2.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f7bf7ed30bcda9bbc9962f187081c5dfa6aa07e06c3a59486bc573b5def35914", size = 2548356, upload-time = "2025-07-30T18:53:38.116Z" },
{ url = "https://files.pythonhosted.org/packages/79/b3/8944acd78ff37a2effcdaf1d6163179e38c46c67c98bb1a68e93a75eb2c2/granian-2.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3152037d799ea97e5736de054a48bf75368fb79b7cfee7e6aa46de1076a43882", size = 3091535, upload-time = "2025-07-30T18:53:39.514Z" },
{ url = "https://files.pythonhosted.org/packages/ce/49/cf0c89eaa41ac81271e3ae33834f71db945cc09dba609f6dc0e75247fd35/granian-2.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:9a53151c2d31dbcf1acbe6af89ce0282387614b6401650d511ca4260ba0e03c1", size = 2977716, upload-time = "2025-07-30T18:53:41.03Z" },
{ url = "https://files.pythonhosted.org/packages/47/58/e828bd5a02c412484b4056cef9aa505b014cc0bb1882f5dbdaf26782f147/granian-2.5.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:8f9918bee3c21eb1410f4323440d76eaa0c2d2e6ca4fa3e3a20d07cc54b788f6", size = 3094250, upload-time = "2025-07-30T18:53:42.495Z" },
{ url = "https://files.pythonhosted.org/packages/ab/ba/e70f0de5cd6e5bca15ea5e5bc6c5598d34f9d4e9e6707e79e6edb63f1fac/granian-2.5.0-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:c28a34951c1ed8eea97948882bdbc374ce111be5a59293693613d25043ba1313", size = 3458806, upload-time = "2025-07-30T18:53:44.308Z" },
{ url = "https://files.pythonhosted.org/packages/a1/c5/4d45042c86a924703f0c9617859742e929cdfe31b644bb16f9845c75342c/granian-2.5.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:944ea3bd400a7ccc8129835eda65bd6a37f8fb77828f4e6ded2f06827d6ec25f", size = 3254382, upload-time = "2025-07-30T18:53:45.769Z" },
{ url = "https://files.pythonhosted.org/packages/ab/8e/bbe7564e28385ebb35e94dd7127238b9ba5c29ee09791f3f69d77b3985d4/granian-2.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9f6d080e45735dd93e4c60a79e42ee9ed37124a9580a08292d83b0961c705e39", size = 2312124, upload-time = "2025-07-30T18:53:47.204Z" },
{ url = "https://files.pythonhosted.org/packages/17/ee/97010d532c4ac48fb2c6dd03e296c8d6ad5829e09f399bfd0a33b6098953/granian-2.5.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a80f23e904f23ca9a90d613444a477a8df8bb2ef1df7bf279ffa6ab7cbbf042a", size = 3034092, upload-time = "2025-07-30T18:54:46.212Z" },
{ url = "https://files.pythonhosted.org/packages/08/f9/263fd8d1d2f0904ead415a19a1b05980180ee647edcb6b0727e2a942e4ad/granian-2.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:53b10f7996c9a732cb3e4cf30890badbac5f9ec4baa2898851a68100767cc754", size = 2601634, upload-time = "2025-07-30T18:54:47.765Z" },
{ url = "https://files.pythonhosted.org/packages/e7/a0/2a3348f6a1291a50cc0b6535b6cdf7fbb73998a5ced6536c0026834a781d/granian-2.5.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f1a05836852ff70745ec094940d9edc62bb3f1a1f7ffb8ee692d6727ebc8b95", size = 3216203, upload-time = "2025-07-30T18:54:49.404Z" },
{ url = "https://files.pythonhosted.org/packages/d9/5f/f18826ae61861c6e20a1887a359c71074f423e4cd7237faf186618d0f7ee/granian-2.5.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3ce4c44ebb949980cc02e0c8a823fb4352c94f2443004fe4c39eb0262fcb2e6e", size = 3106525, upload-time = "2025-07-30T18:54:50.927Z" },
{ url = "https://files.pythonhosted.org/packages/c7/51/6776580a11e0966af4919735072b7d6fd95feea2907753a77046f1f8fbe3/granian-2.5.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5a8eea72a37c582fe2653587f5b4bb5323bd8882fcbccff3054311b0735d3814", size = 3106320, upload-time = "2025-07-30T18:54:53.034Z" },
{ url = "https://files.pythonhosted.org/packages/37/8f/9910ac8585fe0f8f0d55bb197a08cccea27e7cd778d059302e5d4c78dfdf/granian-2.5.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:168762227d94b74dddff066b2f3519f22426e09f8394ed1a2f48072f80be9275", size = 3533584, upload-time = "2025-07-30T18:54:54.837Z" },
{ url = "https://files.pythonhosted.org/packages/3c/49/24c811233d11756182ad13dd30e5323dd88faeb76879493dccb484f8d8fe/granian-2.5.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:534a0922c460a8bf9c85937edbc7aac00497d05b64bf9ccc5f5b93006882ecd7", size = 3272305, upload-time = "2025-07-30T18:54:56.948Z" },
{ url = "https://files.pythonhosted.org/packages/39/07/a39bb33f26c422d5548d9b928fc06da41f50314d3f47ed443791be51d147/granian-2.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6bd767b7f456472eef6597570c588ce8ff2351809cd64ecbb5e0b4f41f74044d", size = 2329707, upload-time = "2025-07-30T18:54:58.531Z" },
{ url = "https://files.pythonhosted.org/packages/0f/20/d9dbfca0979dfdb6a964520c2a4c92591ce33abe00747bcaf94c337bb438/granian-2.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2fcb9fd1c859d7576ff5262f01ed53981c70c831b3f64490333a4012c51aa338", size = 2847793, upload-time = "2025-08-26T16:08:32.889Z" },
{ url = "https://files.pythonhosted.org/packages/e5/ea/227d1e42ad86505bf49301b0047697f47e9b4ed61d8ce921d6f8d888a3e5/granian-2.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fa641231b0e9ee89c72dcd3610fba9ffa0aa89ddab62a3da460d5bce2f13c1d", size = 2550025, upload-time = "2025-08-26T16:08:34.575Z" },
{ url = "https://files.pythonhosted.org/packages/d7/b7/0bea2dae12f78b0fd4dd9f2219817ba2417cd4a3a9d9000c088554986a56/granian-2.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a087b155f595c127f3dc331bc067ece1d55da5a5984649bf708cdee4b65d71cb", size = 3037651, upload-time = "2025-08-26T16:08:35.977Z" },
{ url = "https://files.pythonhosted.org/packages/9e/9e/5000edbd59a0f802930ed217a42f14a66835dda075c55d3ba433088fb2ef/granian-2.5.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:873eb7e402ca59484ee8e41d495c6e8c7a659dd4bea4a72f711f6f5d591c6400", size = 2860856, upload-time = "2025-08-26T16:08:37.363Z" },
{ url = "https://files.pythonhosted.org/packages/b1/ee/bd4d7746523105dd6750607b7c21577f2562d870ab24bb1f97a828d98f29/granian-2.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b4319ce47b218bbf10e39affdf935f3caaf996f1c82fd9539bbe1086e9b636a", size = 3164156, upload-time = "2025-08-26T16:08:39.164Z" },
{ url = "https://files.pythonhosted.org/packages/69/0a/027bc8299d0d8e46b0a2529eb23937b934bd9e5f143311d4e3eb63bf4b96/granian-2.5.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:56651c3689daf8f133a786ce43c8f24926a75bdf61ed1f205c4648940dbb6e22", size = 2932547, upload-time = "2025-08-26T16:08:41.192Z" },
{ url = "https://files.pythonhosted.org/packages/22/9f/65e1e328824fbbd2836969b8f8aa938df745df0219cb45201d0ab5a816a7/granian-2.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ec827847fd41241f294e47eeb58b9db22eca0375f1f3bcefed55718188c31748", size = 2914942, upload-time = "2025-08-26T16:08:43.092Z" },
{ url = "https://files.pythonhosted.org/packages/c9/3f/4dda1c00f3420278268c3e65caa4c6de28e67443f71e5177c8a77fb7b4c5/granian-2.5.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f40ea10e7348011ca85edeeb04a2afb2eae6baf775a493222d607fa7a3b175cd", size = 3150697, upload-time = "2025-08-26T16:08:44.866Z" },
{ url = "https://files.pythonhosted.org/packages/f0/dc/0cea90f9654231dcaf428137d93d62827cc22de45adc952d8bfd006af08b/granian-2.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:73a5657783cc4eaa1ea68599f4b471c00e573d31c8c66c9b8cba78baaa258e87", size = 3197458, upload-time = "2025-08-26T16:08:46.37Z" },
{ url = "https://files.pythonhosted.org/packages/04/01/ad821503d38a12f1028462ba702956a59f3172b6527960112787e9d85b7a/granian-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:bfa7d98c32757a1e079917607f8b65de4b6c886411efedbb03040dc7860121b1", size = 2187133, upload-time = "2025-08-26T16:08:47.735Z" },
{ url = "https://files.pythonhosted.org/packages/d7/09/faab23dc6f49fa34dd63870080e74016fbfc2e0dd3f34340f219da0dcd5f/granian-2.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9c609c0f41f5f3eaccf2c2b6e190b40f75686cb9ebda8db09133b10457ae218a", size = 2832263, upload-time = "2025-08-26T16:08:49.113Z" },
{ url = "https://files.pythonhosted.org/packages/53/6c/d8564ab4eb4b84408739c930888a6f7b6db80fed8a5a1fde636d6887bdfc/granian-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4462fa0a2ce1b419fdd1dc1039c29101dd84537bbbf1358e99ee15b35683c88e", size = 2538574, upload-time = "2025-08-26T16:08:50.755Z" },
{ url = "https://files.pythonhosted.org/packages/d5/ef/62c7bc3e79d5bde01d5f452acf2974848514f92ad71c28dbd3688536b6f3/granian-2.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4ebfeb337f2f27cb7a5de6c5ae6ff309bb699cf4ac1f1714685650fb2daffeb", size = 3025666, upload-time = "2025-08-26T16:08:53.239Z" },
{ url = "https://files.pythonhosted.org/packages/3c/cd/bddcc1d15dd72530ac9fac930ebc0d94017020d35ef4497c871ff2adb7fa/granian-2.5.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a45be4bc3f387fcf90ab332e455ef59c7f35ae96bc64ed9e6cdc751c0c7530b7", size = 2858379, upload-time = "2025-08-26T16:08:55.239Z" },
{ url = "https://files.pythonhosted.org/packages/1f/73/0bdf768f57720182301e818e01a368048de9777ce775c16db91ebd1bd593/granian-2.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78172a29057f6d9794fd89c964eeb849dab7bc6b5f558a67daa0f10ed7fa742d", size = 3159084, upload-time = "2025-08-26T16:08:56.663Z" },
{ url = "https://files.pythonhosted.org/packages/08/6a/20121270db16ea296372682e899a27a490445e1155eb49b7b3dee35a41b3/granian-2.5.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d086dc3509888b2855cfd7107cc30279ca004a8b40ab6e5bf12a6925649bf632", size = 2933814, upload-time = "2025-08-26T16:08:58.729Z" },
{ url = "https://files.pythonhosted.org/packages/d1/fe/2df61f54d24d53e2f326d6fad3ee409fcfd11424cf41d96c217a57c8219b/granian-2.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d45175bdf63ad9072a54c925f27114554ea3457d4a84d58cda84cb815d57178d", size = 2911839, upload-time = "2025-08-26T16:09:00.237Z" },
{ url = "https://files.pythonhosted.org/packages/02/d1/da10aa21f539cafa7dfd2f829c2264549dc6778c471c4855cf91c5b69b01/granian-2.5.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:20dcdd87134ea43a5deea9926ccf94b841a5d212801d4901e5316c3f0fee7a65", size = 3137107, upload-time = "2025-08-26T16:09:01.853Z" },
{ url = "https://files.pythonhosted.org/packages/2e/51/f94c8a26871f5e5f74d03f6bb4c1c588f0f11385107435eceb7d5b3a9834/granian-2.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:eec44e5687d90b66b27477bc9b51e272cf884ff0260d31222a6a0651600c5cf5", size = 3210850, upload-time = "2025-08-26T16:09:03.177Z" },
{ url = "https://files.pythonhosted.org/packages/9f/c5/4010b30f02f0b3627aedc0e246bd7a6563f2313529e3e79fa1d592216161/granian-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:999d6dbe6ddf7e96484848da6b1ecd41e77f29e5f21a7155186c11d1f258f1f2", size = 2189562, upload-time = "2025-08-26T16:09:04.757Z" },
{ url = "https://files.pythonhosted.org/packages/7c/dd/9b22b8e52d17c5472c061c81a14bb3b4cbb80ce37df33465775ff28781d5/granian-2.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:673fd75d5c523238842abd2dfbbf13733f450e4734a16b01aedf2bdf8cf69881", size = 2831999, upload-time = "2025-08-26T16:09:06.379Z" },
{ url = "https://files.pythonhosted.org/packages/09/e5/2d09f60e2140b738518356690877e72d8be64726c652ec849cdfcd1f794a/granian-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d3c2275b1346e6445cd92fef3a67f5de8871150f3c71d20209c0f0974ce690d", size = 2538199, upload-time = "2025-08-26T16:09:07.781Z" },
{ url = "https://files.pythonhosted.org/packages/69/8a/29f07695cdb6f08e80bf7748f9bfb68d1e0f70c57e70049780d657caa1f4/granian-2.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04e728c12c0b3181bec64b09948e29045cf305128571ec2119c68b9904222b21", size = 3025728, upload-time = "2025-08-26T16:09:09.239Z" },
{ url = "https://files.pythonhosted.org/packages/a2/d3/0035137dc8f2637018fbb3e5cb67c83bc4ff44f18b9ccb7bd6cec288471d/granian-2.5.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24d7ad8616f5871a2bae40cfbc9476c65a77640c0eda0b4cb2fda882d773d451", size = 2858025, upload-time = "2025-08-26T16:09:10.763Z" },
{ url = "https://files.pythonhosted.org/packages/da/75/19e8ce4c39ba3d84603e1d31a06b2f9af28e747ad340aa344468b4f87faa/granian-2.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:914e571434bbfa0226e16a14409a088031cac7015c191614e936c64d60941744", size = 3158731, upload-time = "2025-08-26T16:09:12.263Z" },
{ url = "https://files.pythonhosted.org/packages/dd/ea/a280c781a3749988a46c0507c9964aad9f774e9564870401fc96167a55c6/granian-2.5.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a78afa9b7e328557ca3ec6cc7970c94cc7c7a2a1cb5c48801a884df14169d657", size = 2933432, upload-time = "2025-08-26T16:09:13.695Z" },
{ url = "https://files.pythonhosted.org/packages/cf/dd/2ff23d8c338eaa47f081103c2bc33b1be403a9c5bc0fe18c5815cc86dd9d/granian-2.5.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:32974ba79997d50dca0ecaee8068b71630a0afbcb1b2f2aaa1a45d01a4fe81d3", size = 2911297, upload-time = "2025-08-26T16:09:15.134Z" },
{ url = "https://files.pythonhosted.org/packages/dc/19/9306cd660df0492a59a786b19f0689e4b5052d842541326ff3deb1444a9f/granian-2.5.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:4b1adddd027ec83a8000d7ea3dd3f7c7094e811f5a76a057d72e6f97d5f520ba", size = 3137004, upload-time = "2025-08-26T16:09:16.548Z" },
{ url = "https://files.pythonhosted.org/packages/46/10/ffe61ed0ca7ce2cb5bf7e37f31ebe27d1bd2693b4f05389db8f5b15f2f03/granian-2.5.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8dbbcba5b3a0b76c4408106260b3f9a13d5946b611303c7f0916c60a5efb6ff5", size = 3210685, upload-time = "2025-08-26T16:09:18.565Z" },
{ url = "https://files.pythonhosted.org/packages/7e/60/382a0a22885d6be74678f88a570b258c379111fcbccd79de934cfd8496c3/granian-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:f56f32e7668b5d8b2f76c56a001b0053c775362d3117288cdbb1fb54afb4403c", size = 2189261, upload-time = "2025-08-26T16:09:20.349Z" },
{ url = "https://files.pythonhosted.org/packages/58/d1/9af9f258c60fffc45901746879d008ed1303edea45cc150e6c9a28709c76/granian-2.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:913343c63ca84c76f864b73170fe9b13e197e081b70d0f8264d0e7ba305f24bd", size = 2769579, upload-time = "2025-08-26T16:09:21.712Z" },
{ url = "https://files.pythonhosted.org/packages/bc/cf/8f58e3e47bc9cc78e5beb181916434b4601426c66c062ab8a8217626aad9/granian-2.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:576f539cb5acb35b24ef1106e9be34b53f1b9c8bd87e60d90e371ddb3ed1f5af", size = 2487354, upload-time = "2025-08-26T16:09:23.74Z" },
{ url = "https://files.pythonhosted.org/packages/08/83/b6fae91a9306aef0670ebf8b05a5a61624b8bcd9899a351fb46c03554b6d/granian-2.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bb9aeadd6c843dc08c59f41f8e5c3de5f463eef069544ae2e18bea08d2158cb", size = 3010060, upload-time = "2025-08-26T16:09:25.213Z" },
{ url = "https://files.pythonhosted.org/packages/52/f9/5ade2b1f7d0f4a8b45a64aa533234df07d721e0a9d8495070324850be0c9/granian-2.5.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:19fd929320f1fa9ddda569166a97427dc7f0cd92819bba6ca78e10435f4d7c02", size = 2787283, upload-time = "2025-08-26T16:09:27.059Z" },
{ url = "https://files.pythonhosted.org/packages/7a/a4/2c87003d9fa902488b7da880da5eb7f6a270519e82d967f422ac89de3fcb/granian-2.5.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:ce8f94dfd3872fd6db5f18040b66e64c82506d19cb908a98f152ec6a58360840", size = 2905213, upload-time = "2025-08-26T16:09:28.97Z" },
{ url = "https://files.pythonhosted.org/packages/0b/87/5db2bb3b2607bdbf5880e42e16e7c66713915f88500493bfb5c53f665cc8/granian-2.5.1-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:f69b103556075f0156f796ee80bfcc7ad7743821b54dc33bc2e4ca20ed2b65ce", size = 3119807, upload-time = "2025-08-26T16:09:30.81Z" },
{ url = "https://files.pythonhosted.org/packages/11/20/689ca4eea01f907fdc740b0a344e650fd33cf36ee62326deedcd13d58f8a/granian-2.5.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:3cab879ebff5efd65782200783c73e8ee963eaee59a4a0f33351c8cdb35656a9", size = 3198431, upload-time = "2025-08-26T16:09:32.79Z" },
{ url = "https://files.pythonhosted.org/packages/3b/d1/af6bee60a27ac77c5c79f51d60d15c94e98af7408d661ef3343cd6ff14f6/granian-2.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:ea6303210fde16c1ad2b2575d396a218ca7195a5fb64640ccbcd6f9fb435c3a1", size = 2180388, upload-time = "2025-08-26T16:09:34.229Z" },
{ url = "https://files.pythonhosted.org/packages/53/47/6bb6880cd97cc992185e404c216c85748d287bfe16dbc0c2e418935b6a3c/granian-2.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:08d92bdc91d91f0b5377a932faea36a640659994aa144d264995418992a4e01e", size = 2847147, upload-time = "2025-08-26T16:10:40.115Z" },
{ url = "https://files.pythonhosted.org/packages/1b/19/d606fa7383d5d20c7d8fe861ae07c61947c1bc41ef38cb0b2ef50b56dbd1/granian-2.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:7c6169cd8d19f6d8ef4b7b67afe618b8a5ceafd9ac7430da7dadb282c1a35f67", size = 2547108, upload-time = "2025-08-26T16:10:41.738Z" },
{ url = "https://files.pythonhosted.org/packages/27/00/7b8ff7963510fbcb0fd6971eb098ad3099d529550347eb4bf2a24b3c6ea8/granian-2.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a469d1fd32923414926bfd4cc59c3e53bcfddbcea38409b09cbb0caf8823c75", size = 3158505, upload-time = "2025-08-26T16:10:43.504Z" },
{ url = "https://files.pythonhosted.org/packages/84/0a/a59e2ae4eacb60456d186627714268741b1c25eacaa225d6a74853811337/granian-2.5.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:709014d3d103a843fe7db83ed77ad4781cba414c191428be7f94c5ada7151990", size = 2925033, upload-time = "2025-08-26T16:10:45.425Z" },
{ url = "https://files.pythonhosted.org/packages/28/6e/c7952d669913fd876bc1428e8acc662ed2fdda4e23a4e36ca449ec843401/granian-2.5.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2bffcf01b067b109bf6e15410d0a7ea6bad45d69cb51b0661435815b57e71e23", size = 2913947, upload-time = "2025-08-26T16:10:47.641Z" },
{ url = "https://files.pythonhosted.org/packages/d9/97/b25bb52f3b43bd1baa3190b8bb7c837316e5bbca4c9a86c46c02d59f896e/granian-2.5.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3c822ec0c88cdb5be7323f72e0a78ff29e36a8dec5c2c60e83797173562cf395", size = 3171770, upload-time = "2025-08-26T16:10:49.572Z" },
{ url = "https://files.pythonhosted.org/packages/5b/1e/e6d7c65d4745ce429daf396685345bde8fb82d52b148fb8c8434f5f98298/granian-2.5.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:67bcecb791de0d63fed6d0c2c76efcdc12d046e63d9db3edb3ae3bf9881a3105", size = 3192802, upload-time = "2025-08-26T16:10:51.3Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d5/c3592c66f2409f8c9f6867314f74d0bd9f9ad8a8c41bde49b97923875484/granian-2.5.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:985a3b184a144767e3aaa836d4ff8f9a1ae20cedebc363529dce3e7a0c795f6e", size = 2195202, upload-time = "2025-08-26T16:10:53.306Z" },
]
[package.optional-dependencies]
reload = [
{ name = "watchfiles" },
]
uvloop = [
{ name = "uvloop", marker = "platform_python_implementation == 'CPython' and sys_platform != 'win32'" },
]
[[package]]
name = "greenlet"
@@ -1585,14 +1627,14 @@ wheels = [
[[package]]
name = "griffe"
version = "1.12.1"
version = "1.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama" },
]
sdist = { url = "https://files.pythonhosted.org/packages/81/ca/29f36e00c74844ae50d139cf5a8b1751887b2f4d5023af65d460268ad7aa/griffe-1.12.1.tar.gz", hash = "sha256:29f5a6114c0aeda7d9c86a570f736883f8a2c5b38b57323d56b3d1c000565567", size = 411863, upload-time = "2025-08-14T21:08:15.38Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/b5/23b91f22b7b3a7f8f62223f6664946271c0f5cb4179605a3e6bbae863920/griffe-1.13.0.tar.gz", hash = "sha256:246ea436a5e78f7fbf5f24ca8a727bb4d2a4b442a2959052eea3d0bfe9a076e0", size = 412759, upload-time = "2025-08-26T13:27:11.422Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/f2/4fab6c3e5bcaf38a44cc8a974d2752eaad4c129e45d6533d926a30edd133/griffe-1.12.1-py3-none-any.whl", hash = "sha256:2d7c12334de00089c31905424a00abcfd931b45b8b516967f224133903d302cc", size = 138940, upload-time = "2025-08-14T21:08:13.382Z" },
{ url = "https://files.pythonhosted.org/packages/aa/8c/b7cfdd8dfe48f6b09f7353323732e1a290c388bd14f216947928dc85f904/griffe-1.13.0-py3-none-any.whl", hash = "sha256:470fde5b735625ac0a36296cd194617f039e9e83e301fcbd493e2b58382d0559", size = 139365, upload-time = "2025-08-26T13:27:09.882Z" },
]
[[package]]
@@ -1869,6 +1911,43 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0a/66/7f8c48009c72d73bc6bbe6eb87ac838d6a526146f7dab14af671121eb379/invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820", size = 160274, upload-time = "2023-07-12T18:05:16.294Z" },
]
[[package]]
name = "ipdb"
version = "0.13.13"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "decorator" },
{ name = "ipython" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042, upload-time = "2023-03-09T15:40:57.487Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130, upload-time = "2023-03-09T15:40:55.021Z" },
]
[[package]]
name = "ipykernel"
version = "6.30.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "appnope", marker = "sys_platform == 'darwin'" },
{ name = "comm" },
{ name = "debugpy" },
{ name = "ipython" },
{ name = "jupyter-client" },
{ name = "jupyter-core" },
{ name = "matplotlib-inline" },
{ name = "nest-asyncio" },
{ name = "packaging" },
{ name = "psutil" },
{ name = "pyzmq" },
{ name = "tornado" },
{ name = "traitlets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bb/76/11082e338e0daadc89c8ff866185de11daf67d181901038f9e139d109761/ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b", size = 166260, upload-time = "2025-08-04T15:47:35.018Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4", size = 117484, upload-time = "2025-08-04T15:47:32.622Z" },
]
[[package]]
name = "ipython"
version = "9.4.0"
@@ -2067,6 +2146,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
]
[[package]]
name = "jupyter-client"
version = "8.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jupyter-core" },
{ name = "python-dateutil" },
{ name = "pyzmq" },
{ name = "tornado" },
{ name = "traitlets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" },
]
[[package]]
name = "jupyter-core"
version = "5.8.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "platformdirs" },
{ name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" },
{ name = "traitlets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" },
]
[[package]]
name = "kiwisolver"
version = "1.4.9"
@@ -2151,7 +2260,7 @@ wheels = [
[[package]]
name = "langchain-community"
version = "0.3.27"
version = "0.3.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -2167,14 +2276,14 @@ dependencies = [
{ name = "sqlalchemy" },
{ name = "tenacity" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5c/76/200494f6de488217a196c4369e665d26b94c8c3642d46e2fd62f9daf0a3a/langchain_community-0.3.27.tar.gz", hash = "sha256:e1037c3b9da0c6d10bf06e838b034eb741e016515c79ef8f3f16e53ead33d882", size = 33237737, upload-time = "2025-07-02T18:47:02.329Z" }
sdist = { url = "https://files.pythonhosted.org/packages/08/76/35b698e00fd206eba44ca46d57860484e4b9f0e050fabac8d027e755935c/langchain_community-0.3.28.tar.gz", hash = "sha256:c97e03d91cade6c9fb73d756119744e1d4c4ea4b6b0a09f6faadfbb7360d335e", size = 33238079, upload-time = "2025-08-26T17:04:24.616Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/bc/f8c7dae8321d37ed39ac9d7896617c4203248240a4835b136e3724b3bb62/langchain_community-0.3.27-py3-none-any.whl", hash = "sha256:581f97b795f9633da738ea95da9cb78f8879b538090c9b7a68c0aed49c828f0d", size = 2530442, upload-time = "2025-07-02T18:47:00.246Z" },
{ url = "https://files.pythonhosted.org/packages/db/6e/735e7e5376908b8dad204fa76d96a3e785d06c7b701660ad4f5efb6022cb/langchain_community-0.3.28-py3-none-any.whl", hash = "sha256:52e437b8f4e899ff59fb90c54b5320bf99153da34f214488ebacdbc969a50faf", size = 2530800, upload-time = "2025-08-26T17:04:21.873Z" },
]
[[package]]
name = "langchain-core"
version = "0.3.74"
version = "0.3.75"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jsonpatch" },
@@ -2185,9 +2294,9 @@ dependencies = [
{ name = "tenacity" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/c6/5d755a0f1f4857abbe5ea6f5907ed0e2b5df52bf4dde0a0fd768290e3084/langchain_core-0.3.74.tar.gz", hash = "sha256:ff604441aeade942fbcc0a3860a592daba7671345230c2078ba2eb5f82b6ba76", size = 569553, upload-time = "2025-08-07T20:47:05.094Z" }
sdist = { url = "https://files.pythonhosted.org/packages/06/63/270b71a23e849984505ddc7c5c9fd3f4bd9cb14b1a484ee44c4e51c33cc2/langchain_core-0.3.75.tar.gz", hash = "sha256:ab0eb95a06ed6043f76162e6086b45037690cb70b7f090bd83b5ebb8a05b70ed", size = 570876, upload-time = "2025-08-26T15:24:12.246Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/26/545283681ac0379d31c7ad0bac5f195e1982092d76c65ca048db9e3cec0e/langchain_core-0.3.74-py3-none-any.whl", hash = "sha256:088338b5bc2f6a66892f9afc777992c24ee3188f41cbc603d09181e34a228ce7", size = 443453, upload-time = "2025-08-07T20:47:03.853Z" },
{ url = "https://files.pythonhosted.org/packages/fb/42/0d0221cce6f168f644d7d96cb6c87c4e42fc55d2941da7a36e970e3ab8ab/langchain_core-0.3.75-py3-none-any.whl", hash = "sha256:03ca1fadf955ee3c7d5806a841f4b3a37b816acea5e61a7e6ba1298c05eea7f5", size = 443986, upload-time = "2025-08-26T15:24:10.883Z" },
]
[[package]]
@@ -2204,7 +2313,7 @@ wheels = [
[[package]]
name = "langsmith"
version = "0.4.16"
version = "0.4.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -2215,9 +2324,9 @@ dependencies = [
{ name = "requests-toolbelt" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/fb/a0fab0ce0bb46aaae9703c1fb814b4ac7cbc2d75adc51e8689f1b34ac08d/langsmith-0.4.16.tar.gz", hash = "sha256:a94f374c7fa0f406757f95f311e84873258563961e1af0ba8996411822cd7241", size = 930411, upload-time = "2025-08-22T15:45:16.56Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e9/65/4e79ad22cc12b31a87bdcf96b1ca5ddabe42a8494eda20e124d044d5562e/langsmith-0.4.18.tar.gz", hash = "sha256:c1340371119f66b7c506810c5998db3669cd04f018a276288d80b91169a68ccc", size = 931753, upload-time = "2025-08-26T17:00:05.901Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/ed/7a48189bdad850cfd47df671204c31779dd190de6bc681f169d4535f852e/langsmith-0.4.16-py3-none-any.whl", hash = "sha256:9ba95ed09b057dfe227e882f5446e1824bfc9f2c89de542ee6f0f8d90ab953a7", size = 375761, upload-time = "2025-08-22T15:45:14.82Z" },
{ url = "https://files.pythonhosted.org/packages/a8/73/91a506e17bb1bc6d20c2c04cf7b459dc58951bfbfe7f97f2c952646b4500/langsmith-0.4.18-py3-none-any.whl", hash = "sha256:ad63154f503678356aadf5b999f40393b4bbd332aee2d04cde3e431c61f2e1c2", size = 376444, upload-time = "2025-08-26T17:00:03.564Z" },
]
[[package]]
@@ -2286,81 +2395,60 @@ dependencies = [
]
[package.optional-dependencies]
all = [
{ name = "autoflake" },
{ name = "black" },
{ name = "docker" },
{ name = "fastapi" },
{ name = "google-cloud-profiler" },
{ name = "granian", extra = ["reload"] },
{ name = "isort" },
{ name = "langchain" },
{ name = "langchain-community" },
{ name = "locust" },
{ name = "pexpect" },
{ name = "pg8000" },
{ name = "pgvector" },
{ name = "pinecone", extra = ["asyncio"] },
{ name = "pre-commit" },
{ name = "psycopg2" },
{ name = "psycopg2-binary" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-order" },
{ name = "redis" },
{ name = "turbopuffer" },
{ name = "uvicorn" },
{ name = "uvloop", marker = "sys_platform != 'win32'" },
{ name = "wikipedia" },
]
bedrock = [
{ name = "aioboto3" },
{ name = "boto3" },
]
cloud-tool-sandbox = [
{ name = "e2b-code-interpreter" },
{ name = "modal" },
]
desktop = [
{ name = "aiosqlite" },
{ name = "docker" },
{ name = "fastapi" },
{ name = "langchain" },
{ name = "langchain-community" },
{ name = "locust" },
{ name = "pgvector" },
{ name = "pyright" },
{ name = "sqlite-vec" },
{ name = "uvicorn" },
{ name = "websockets" },
{ name = "wikipedia" },
]
dev = [
{ name = "autoflake" },
{ name = "black" },
{ name = "black", extra = ["jupyter"] },
{ name = "ipdb" },
{ name = "ipykernel" },
{ name = "isort" },
{ name = "locust" },
{ name = "pexpect" },
{ name = "pre-commit" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-json-report" },
{ name = "pytest-mock" },
{ name = "pytest-order" },
]
experimental = [
{ name = "google-cloud-profiler" },
{ name = "granian", extra = ["reload"] },
{ name = "uvloop", marker = "sys_platform != 'win32'" },
{ name = "granian", extra = ["reload", "uvloop"] },
{ name = "uvloop" },
]
external-tools = [
{ name = "docker" },
{ name = "firecrawl-py" },
{ name = "langchain" },
{ name = "langchain-community" },
{ name = "turbopuffer" },
{ name = "wikipedia" },
]
google = [
{ name = "google-genai" },
]
modal = [
{ name = "modal" },
]
pinecone = [
{ name = "pinecone", extra = ["asyncio"] },
]
@@ -2383,25 +2471,20 @@ sqlite = [
{ name = "aiosqlite" },
{ name = "sqlite-vec" },
]
tests = [
{ name = "pytest-asyncio" },
{ name = "wikipedia" },
]
[package.metadata]
requires-dist = [
{ name = "aioboto3", marker = "extra == 'bedrock'", specifier = ">=14.3.0" },
{ name = "aiomultiprocess", specifier = ">=0.9.1" },
{ name = "aiosqlite", marker = "extra == 'desktop'", specifier = ">=0.21.0" },
{ name = "aiosqlite", marker = "extra == 'sqlite'", specifier = ">=0.21.0" },
{ name = "alembic", specifier = ">=1.13.3" },
{ name = "anthropic", specifier = ">=0.49.0" },
{ name = "apscheduler", specifier = ">=3.11.0" },
{ name = "asyncpg", marker = "extra == 'postgres'", specifier = ">=0.30.0" },
{ name = "autoflake", marker = "extra == 'all'", specifier = ">=2.3.0" },
{ name = "autoflake", marker = "extra == 'dev'", specifier = ">=2.3.0" },
{ name = "black", marker = "extra == 'all'", specifier = ">=24.2.0" },
{ name = "black", marker = "extra == 'dev'", specifier = ">=24.2.0" },
{ name = "black", extras = ["jupyter"], specifier = ">=24.2.0" },
{ name = "black", extras = ["jupyter"], marker = "extra == 'dev'", specifier = ">=24.4.2" },
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.36.24" },
{ name = "brotli", specifier = ">=1.1.0" },
{ name = "certifi", specifier = ">=2025.6.15" },
@@ -2409,48 +2492,41 @@ requires-dist = [
{ name = "composio-core", specifier = ">=0.7.7" },
{ name = "datamodel-code-generator", extras = ["http"], specifier = ">=0.25.0" },
{ name = "demjson3", specifier = ">=3.0.6" },
{ name = "docker", marker = "extra == 'all'", specifier = ">=7.1.0" },
{ name = "docker", marker = "extra == 'desktop'", specifier = ">=7.1.0" },
{ name = "docker", marker = "extra == 'external-tools'", specifier = ">=7.1.0" },
{ name = "docstring-parser", specifier = ">=0.16,<0.17" },
{ name = "e2b-code-interpreter", marker = "extra == 'cloud-tool-sandbox'", specifier = "==1.5.2" },
{ name = "e2b-code-interpreter", marker = "extra == 'cloud-tool-sandbox'", specifier = ">=1.0.3" },
{ name = "faker", specifier = ">=36.1.0" },
{ name = "fastapi", marker = "extra == 'all'", specifier = ">=0.115.6" },
{ name = "fastapi", marker = "extra == 'desktop'", specifier = ">=0.115.6" },
{ name = "fastapi", marker = "extra == 'server'", specifier = ">=0.115.6" },
{ name = "firecrawl-py", specifier = "==2.16.5" },
{ name = "firecrawl-py", marker = "extra == 'external-tools'", specifier = "==2.16.5" },
{ name = "google-cloud-profiler", marker = "extra == 'all'", specifier = ">=4.1.0" },
{ name = "firecrawl-py", specifier = ">=2.8.0,<3.0.0" },
{ name = "firecrawl-py", marker = "extra == 'external-tools'", specifier = ">=2.8.0,<3.0.0" },
{ name = "google-cloud-profiler", marker = "extra == 'experimental'", specifier = ">=4.1.0" },
{ name = "google-genai", marker = "extra == 'google'", specifier = ">=1.15.0" },
{ name = "granian", extras = ["reload"], marker = "extra == 'all'", specifier = ">=2.3.2" },
{ name = "granian", extras = ["reload"], marker = "extra == 'experimental'", specifier = ">=2.3.2" },
{ name = "granian", extras = ["uvloop", "reload"], marker = "extra == 'experimental'", specifier = ">=2.3.2" },
{ name = "grpcio", specifier = ">=1.68.1" },
{ name = "grpcio-tools", specifier = ">=1.68.1" },
{ name = "html2text", specifier = ">=2020.1.16" },
{ name = "httpx", specifier = ">=0.28.0" },
{ name = "httpx-sse", specifier = ">=0.4.0" },
{ name = "isort", marker = "extra == 'all'", specifier = ">=5.13.2" },
{ name = "ipdb", marker = "extra == 'dev'", specifier = ">=0.13.13" },
{ name = "ipykernel", marker = "extra == 'dev'", specifier = ">=6.29.5" },
{ name = "isort", marker = "extra == 'dev'", specifier = ">=5.13.2" },
{ name = "jinja2", specifier = ">=3.1.5" },
{ name = "langchain", marker = "extra == 'all'", specifier = ">=0.3.7" },
{ name = "langchain", marker = "extra == 'desktop'", specifier = ">=0.3.7" },
{ name = "langchain", marker = "extra == 'external-tools'", specifier = ">=0.3.7" },
{ name = "langchain-community", marker = "extra == 'all'", specifier = ">=0.3.7" },
{ name = "langchain-community", marker = "extra == 'desktop'", specifier = ">=0.3.7" },
{ name = "langchain-community", marker = "extra == 'external-tools'", specifier = ">=0.3.7" },
{ name = "letta-client", specifier = ">=0.1.276" },
{ name = "letta-client", specifier = ">=0.1.277" },
{ name = "llama-index", specifier = ">=0.12.2" },
{ name = "llama-index-embeddings-openai", specifier = ">=0.3.1" },
{ name = "locust", marker = "extra == 'all'", specifier = ">=2.31.5" },
{ name = "locust", marker = "extra == 'desktop'", specifier = ">=2.31.5" },
{ name = "locust", marker = "extra == 'dev'", specifier = ">=2.31.5" },
{ name = "markitdown", extras = ["docx", "pdf", "pptx"], specifier = ">=0.1.2" },
{ name = "marshmallow-sqlalchemy", specifier = ">=1.4.1" },
{ name = "matplotlib", specifier = ">=3.10.1" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.9.4" },
{ name = "mistralai", specifier = ">=1.8.1" },
{ name = "modal", marker = "extra == 'cloud-tool-sandbox'", specifier = ">=1.1.0" },
{ name = "modal", marker = "extra == 'modal'", specifier = ">=1.1.0" },
{ name = "nltk", specifier = ">=3.8.1" },
{ name = "numpy", specifier = ">=2.1.0" },
{ name = "openai", specifier = ">=1.99.9" },
@@ -2461,41 +2537,29 @@ requires-dist = [
{ name = "opentelemetry-sdk", specifier = "==1.30.0" },
{ name = "orjson", specifier = ">=3.11.1" },
{ name = "pathvalidate", specifier = ">=3.2.1" },
{ name = "pexpect", marker = "extra == 'all'", specifier = ">=4.9.0" },
{ name = "pexpect", marker = "extra == 'dev'", specifier = ">=4.9.0" },
{ name = "pg8000", marker = "extra == 'all'", specifier = ">=1.30.3" },
{ name = "pg8000", marker = "extra == 'postgres'", specifier = ">=1.30.3" },
{ name = "pgvector", marker = "extra == 'all'", specifier = ">=0.2.3" },
{ name = "pgvector", marker = "extra == 'desktop'", specifier = ">=0.2.3" },
{ name = "pgvector", marker = "extra == 'postgres'", specifier = ">=0.2.3" },
{ name = "pinecone", extras = ["asyncio"], marker = "extra == 'all'", specifier = ">=7.3.0" },
{ name = "pinecone", extras = ["asyncio"], marker = "extra == 'pinecone'", specifier = ">=7.3.0" },
{ name = "pre-commit", marker = "extra == 'all'", specifier = ">=3.5.0" },
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" },
{ name = "prettytable", specifier = ">=3.9.0" },
{ name = "psycopg2", marker = "extra == 'all'", specifier = ">=2.9.10" },
{ name = "psycopg2", marker = "extra == 'postgres'", specifier = ">=2.9.10" },
{ name = "psycopg2-binary", marker = "extra == 'all'", specifier = ">=2.9.10" },
{ name = "psycopg2-binary", marker = "extra == 'postgres'", specifier = ">=2.9.10" },
{ name = "pydantic", specifier = ">=2.10.6" },
{ name = "pydantic-settings", specifier = ">=2.2.1" },
{ name = "pyhumps", specifier = ">=3.8.0" },
{ name = "pyright", marker = "extra == 'all'", specifier = ">=1.1.347" },
{ name = "pyright", marker = "extra == 'desktop'", specifier = ">=1.1.347" },
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.347" },
{ name = "pytest", marker = "extra == 'all'" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" },
{ name = "pytest-asyncio", marker = "extra == 'all'", specifier = ">=0.24.0" },
{ name = "pytest", marker = "extra == 'dev'" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" },
{ name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.24.0" },
{ name = "pytest-order", marker = "extra == 'all'", specifier = ">=1.2.0" },
{ name = "pytest-json-report", marker = "extra == 'dev'", specifier = ">=1.5.0" },
{ name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.14.0" },
{ name = "pytest-order", marker = "extra == 'dev'", specifier = ">=1.2.0" },
{ name = "python-box", specifier = ">=7.1.1" },
{ name = "python-multipart", specifier = ">=0.0.19" },
{ name = "pytz", specifier = ">=2023.3.post1" },
{ name = "pyyaml", specifier = ">=6.0.1" },
{ name = "questionary", specifier = ">=2.0.1" },
{ name = "redis", marker = "extra == 'all'", specifier = ">=6.2.0" },
{ name = "redis", marker = "extra == 'redis'", specifier = ">=6.2.0" },
{ name = "rich", specifier = ">=13.9.4" },
{ name = "sentry-sdk", extras = ["fastapi"], specifier = "==2.19.1" },
@@ -2509,24 +2573,21 @@ requires-dist = [
{ name = "structlog", specifier = ">=25.4.0" },
{ name = "tavily-python", specifier = ">=0.7.2" },
{ name = "tqdm", specifier = ">=4.66.1" },
{ name = "turbopuffer", marker = "extra == 'all'", specifier = ">=0.5.17" },
{ name = "turbopuffer", marker = "extra == 'external-tools'", specifier = ">=0.5.17" },
{ name = "typer", specifier = ">=0.15.2" },
{ name = "uvicorn", marker = "extra == 'all'", specifier = ">=0.24.0.post1" },
{ name = "uvicorn", marker = "extra == 'desktop'", specifier = ">=0.24.0.post1" },
{ name = "uvicorn", marker = "extra == 'server'", specifier = ">=0.24.0.post1" },
{ name = "uvloop", marker = "sys_platform != 'win32' and extra == 'all'", specifier = ">=0.21.0" },
{ name = "uvloop", marker = "sys_platform != 'win32' and extra == 'experimental'", specifier = ">=0.21.0" },
{ name = "websockets", marker = "extra == 'server'", specifier = ">=12.0" },
{ name = "wikipedia", marker = "extra == 'all'", specifier = ">=1.4.0" },
{ name = "uvloop", marker = "extra == 'experimental'", specifier = ">=0.21.0" },
{ name = "websockets", marker = "extra == 'desktop'" },
{ name = "websockets", marker = "extra == 'server'" },
{ name = "wikipedia", marker = "extra == 'desktop'", specifier = ">=1.4.0" },
{ name = "wikipedia", marker = "extra == 'external-tools'", specifier = ">=1.4.0" },
{ name = "wikipedia", marker = "extra == 'tests'", specifier = ">=1.4.0" },
]
provides-extras = ["postgres", "redis", "pinecone", "dev", "experimental", "server", "cloud-tool-sandbox", "external-tools", "tests", "sqlite", "bedrock", "google", "desktop", "all"]
provides-extras = ["postgres", "redis", "pinecone", "sqlite", "experimental", "server", "bedrock", "google", "dev", "cloud-tool-sandbox", "modal", "external-tools", "desktop"]
[[package]]
name = "letta-client"
version = "0.1.277"
version = "0.1.282"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -2535,9 +2596,9 @@ dependencies = [
{ name = "pydantic-core" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/ad/77af6bc5cebd280acc4c1e877ced058f3480fcd7bcb3986db613039a6485/letta_client-0.1.277.tar.gz", hash = "sha256:112837705c43c7c2c9a6baa99c38a0ad390f8bcb7ad0de5e3b9556990b898427", size = 183930, upload-time = "2025-08-25T20:50:20.217Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ef/11/5b22e4590ef9f9d52af7f282d7d373eefbf80cb35ece72a59acb182a5e65/letta_client-0.1.282.tar.gz", hash = "sha256:dc62b604171172fbab4b7259d769bf7a9b35e35242a37f9d0582376513c17168", size = 184433, upload-time = "2025-08-26T21:59:41.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/95/9bcfaac63d2467f07acfb85ad3497d4e1471fabb84ff95f3c493289cecd2/letta_client-0.1.277-py3-none-any.whl", hash = "sha256:2428ee84ff3cf582c935623dd7678845bfe46e79c75cb9a1a1b6efe284c5bc16", size = 464168, upload-time = "2025-08-25T20:50:18.662Z" },
{ url = "https://files.pythonhosted.org/packages/ec/08/3c16f38f25bb8704724f9336c01628eadaa0e43eb45f8dbdd2165d0aeb4b/letta_client-0.1.282-py3-none-any.whl", hash = "sha256:71a29e4061d9e66817514fab3e970940a9254067776f8c161a9bd325fe9ad5e7", size = 464951, upload-time = "2025-08-26T21:59:39.883Z" },
]
[[package]]
@@ -3128,7 +3189,7 @@ wheels = [
[[package]]
name = "mistralai"
version = "1.9.8"
version = "1.9.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "eval-type-backport" },
@@ -3139,9 +3200,9 @@ dependencies = [
{ name = "pyyaml" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/dc/6f/12296d29c480a4101e2bc092347895ce1a8047b6bbc52f97f124177df0b4/mistralai-1.9.8.tar.gz", hash = "sha256:74eac8b3aee410dffbd8ef0878adb7f593940fa9592d9eb4428da9b067209b22", size = 204432, upload-time = "2025-08-25T16:30:31.354Z" }
sdist = { url = "https://files.pythonhosted.org/packages/37/02/fb484098d29f10e1f55f0c88c15262990d253304b94a5ecedd89e6a68d06/mistralai-1.9.9.tar.gz", hash = "sha256:025ae6f45dba8b7585642bc6fa214316138546a0cac692c6ec8e1187424da54a", size = 204678, upload-time = "2025-08-26T17:41:08.378Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/b3/3c1d449eea89153a77e7093e90e0282d1a718865ae6787c379256b1db288/mistralai-1.9.8-py3-none-any.whl", hash = "sha256:f4874d62932245c438c4fce04aaf740a9d6da651dc598bf660978a21fb73f017", size = 439113, upload-time = "2025-08-25T16:30:29.855Z" },
{ url = "https://files.pythonhosted.org/packages/7b/2c/7c62e67c221a10bfd94b219fe115211109a83e72a996fa343d72b4fa9f84/mistralai-1.9.9-py3-none-any.whl", hash = "sha256:6742fdbf4a277b605287538761e2665b6fb025328676a024868734ffe78ef72f", size = 439470, upload-time = "2025-08-26T17:41:07.05Z" },
]
[[package]]
@@ -3446,7 +3507,7 @@ wheels = [
[[package]]
name = "openai"
version = "1.101.0"
version = "1.102.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -3458,9 +3519,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/00/7c/eaf06b62281f5ca4f774c4cff066e6ddfd6a027e0ac791be16acec3a95e3/openai-1.101.0.tar.gz", hash = "sha256:29f56df2236069686e64aca0e13c24a4ec310545afb25ef7da2ab1a18523f22d", size = 518415, upload-time = "2025-08-21T21:11:01.645Z" }
sdist = { url = "https://files.pythonhosted.org/packages/07/55/da5598ed5c6bdd9939633854049cddc5cbac0da938dfcfcb3c6b119c16c0/openai-1.102.0.tar.gz", hash = "sha256:2e0153bcd64a6523071e90211cbfca1f2bbc5ceedd0993ba932a5869f93b7fc9", size = 519027, upload-time = "2025-08-26T20:50:29.397Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/a6/0e39baa335bbd1c66c7e0a41dbbec10c5a15ab95c1344e7f7beb28eee65a/openai-1.101.0-py3-none-any.whl", hash = "sha256:6539a446cce154f8d9fb42757acdfd3ed9357ab0d34fcac11096c461da87133b", size = 810772, upload-time = "2025-08-21T21:10:59.215Z" },
{ url = "https://files.pythonhosted.org/packages/bd/0d/c9e7016d82c53c5b5e23e2bad36daebb8921ed44f69c0a985c6529a35106/openai-1.102.0-py3-none-any.whl", hash = "sha256:d751a7e95e222b5325306362ad02a7aa96e1fab3ed05b5888ce1c7ca63451345", size = 812015, upload-time = "2025-08-26T20:50:27.219Z" },
]
[[package]]
@@ -3633,55 +3694,55 @@ wheels = [
[[package]]
name = "orjson"
version = "3.11.2"
version = "3.11.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/1d/5e0ae38788bdf0721326695e65fdf41405ed535f633eb0df0f06f57552fa/orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309", size = 5470739, upload-time = "2025-08-12T15:12:28.626Z" }
sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/7d/e295df1ac9920cbb19fb4c1afa800e86f175cb657143aa422337270a4782/orjson-3.11.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:888b64ef7eaeeff63f773881929434a5834a6a140a63ad45183d59287f07fc6a", size = 226502, upload-time = "2025-08-12T15:10:42.284Z" },
{ url = "https://files.pythonhosted.org/packages/65/21/ffb0f10ea04caf418fb4e7ad1fda4b9ab3179df9d7a33b69420f191aadd5/orjson-3.11.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:83387cc8b26c9fa0ae34d1ea8861a7ae6cff8fb3e346ab53e987d085315a728e", size = 115999, upload-time = "2025-08-12T15:10:43.738Z" },
{ url = "https://files.pythonhosted.org/packages/90/d5/8da1e252ac3353d92e6f754ee0c85027c8a2cda90b6899da2be0df3ef83d/orjson-3.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e35f003692c216d7ee901b6b916b5734d6fc4180fcaa44c52081f974c08e17", size = 111563, upload-time = "2025-08-12T15:10:45.301Z" },
{ url = "https://files.pythonhosted.org/packages/4f/81/baabc32e52c570b0e4e1044b1bd2ccbec965e0de3ba2c13082255efa2006/orjson-3.11.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a0a4c29ae90b11d0c00bcc31533854d89f77bde2649ec602f512a7e16e00640", size = 116222, upload-time = "2025-08-12T15:10:46.92Z" },
{ url = "https://files.pythonhosted.org/packages/8d/b7/da2ad55ad80b49b560dce894c961477d0e76811ee6e614b301de9f2f8728/orjson-3.11.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:585d712b1880f68370108bc5534a257b561672d1592fae54938738fe7f6f1e33", size = 118594, upload-time = "2025-08-12T15:10:48.488Z" },
{ url = "https://files.pythonhosted.org/packages/61/be/014f7eab51449f3c894aa9bbda2707b5340c85650cb7d0db4ec9ae280501/orjson-3.11.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d08e342a7143f8a7c11f1c4033efe81acbd3c98c68ba1b26b96080396019701f", size = 120700, upload-time = "2025-08-12T15:10:49.811Z" },
{ url = "https://files.pythonhosted.org/packages/cf/ae/c217903a30c51341868e2d8c318c59a8413baa35af54d7845071c8ccd6fe/orjson-3.11.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c0f84fc50398773a702732c87cd622737bf11c0721e6db3041ac7802a686fb", size = 123433, upload-time = "2025-08-12T15:10:51.06Z" },
{ url = "https://files.pythonhosted.org/packages/57/c2/b3c346f78b1ff2da310dd300cb0f5d32167f872b4d3bb1ad122c889d97b0/orjson-3.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:140f84e3c8d4c142575898c91e3981000afebf0333df753a90b3435d349a5fe5", size = 121061, upload-time = "2025-08-12T15:10:52.381Z" },
{ url = "https://files.pythonhosted.org/packages/00/c8/c97798f6010327ffc75ad21dd6bca11ea2067d1910777e798c2849f1c68f/orjson-3.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96304a2b7235e0f3f2d9363ddccdbfb027d27338722fe469fe656832a017602e", size = 119410, upload-time = "2025-08-12T15:10:53.692Z" },
{ url = "https://files.pythonhosted.org/packages/37/fd/df720f7c0e35694617b7f95598b11a2cb0374661d8389703bea17217da53/orjson-3.11.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d7612bb227d5d9582f1f50a60bd55c64618fc22c4a32825d233a4f2771a428a", size = 392294, upload-time = "2025-08-12T15:10:55.079Z" },
{ url = "https://files.pythonhosted.org/packages/ba/52/0120d18f60ab0fe47531d520372b528a45c9a25dcab500f450374421881c/orjson-3.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a134587d18fe493befc2defffef2a8d27cfcada5696cb7234de54a21903ae89a", size = 134134, upload-time = "2025-08-12T15:10:56.568Z" },
{ url = "https://files.pythonhosted.org/packages/ec/10/1f967671966598366de42f07e92b0fc694ffc66eafa4b74131aeca84915f/orjson-3.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b84455e60c4bc12c1e4cbaa5cfc1acdc7775a9da9cec040e17232f4b05458bd", size = 123745, upload-time = "2025-08-12T15:10:57.907Z" },
{ url = "https://files.pythonhosted.org/packages/43/eb/76081238671461cfd0f47e0c24f408ffa66184237d56ef18c33e86abb612/orjson-3.11.2-cp311-cp311-win32.whl", hash = "sha256:f0660efeac223f0731a70884e6914a5f04d613b5ae500744c43f7bf7b78f00f9", size = 124393, upload-time = "2025-08-12T15:10:59.267Z" },
{ url = "https://files.pythonhosted.org/packages/26/76/cc598c1811ba9ba935171267b02e377fc9177489efce525d478a2999d9cc/orjson-3.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:955811c8405251d9e09cbe8606ad8fdef49a451bcf5520095a5ed38c669223d8", size = 119561, upload-time = "2025-08-12T15:11:00.559Z" },
{ url = "https://files.pythonhosted.org/packages/d8/17/c48011750f0489006f7617b0a3cebc8230f36d11a34e7e9aca2085f07792/orjson-3.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:2e4d423a6f838552e3a6d9ec734b729f61f88b1124fd697eab82805ea1a2a97d", size = 114186, upload-time = "2025-08-12T15:11:01.931Z" },
{ url = "https://files.pythonhosted.org/packages/40/02/46054ebe7996a8adee9640dcad7d39d76c2000dc0377efa38e55dc5cbf78/orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486", size = 226528, upload-time = "2025-08-12T15:11:03.317Z" },
{ url = "https://files.pythonhosted.org/packages/e2/c6/6b6f0b4d8aea1137436546b990f71be2cd8bd870aa2f5aa14dba0fcc95dc/orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1", size = 115931, upload-time = "2025-08-12T15:11:04.759Z" },
{ url = "https://files.pythonhosted.org/packages/ae/05/4205cc97c30e82a293dd0d149b1a89b138ebe76afeca66fc129fa2aa4e6a/orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131", size = 111382, upload-time = "2025-08-12T15:11:06.468Z" },
{ url = "https://files.pythonhosted.org/packages/50/c7/b8a951a93caa821f9272a7c917115d825ae2e4e8768f5ddf37968ec9de01/orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c", size = 116271, upload-time = "2025-08-12T15:11:07.845Z" },
{ url = "https://files.pythonhosted.org/packages/17/03/1006c7f8782d5327439e26d9b0ec66500ea7b679d4bbb6b891d2834ab3ee/orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14", size = 119086, upload-time = "2025-08-12T15:11:09.329Z" },
{ url = "https://files.pythonhosted.org/packages/44/61/57d22bc31f36a93878a6f772aea76b2184102c6993dea897656a66d18c74/orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448", size = 120724, upload-time = "2025-08-12T15:11:10.674Z" },
{ url = "https://files.pythonhosted.org/packages/78/a9/4550e96b4c490c83aea697d5347b8f7eb188152cd7b5a38001055ca5b379/orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c", size = 123577, upload-time = "2025-08-12T15:11:12.015Z" },
{ url = "https://files.pythonhosted.org/packages/3a/86/09b8cb3ebd513d708ef0c92d36ac3eebda814c65c72137b0a82d6d688fc4/orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804", size = 121195, upload-time = "2025-08-12T15:11:13.399Z" },
{ url = "https://files.pythonhosted.org/packages/37/68/7b40b39ac2c1c644d4644e706d0de6c9999764341cd85f2a9393cb387661/orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307", size = 119234, upload-time = "2025-08-12T15:11:15.134Z" },
{ url = "https://files.pythonhosted.org/packages/40/7c/bb6e7267cd80c19023d44d8cbc4ea4ed5429fcd4a7eb9950f50305697a28/orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219", size = 392250, upload-time = "2025-08-12T15:11:16.604Z" },
{ url = "https://files.pythonhosted.org/packages/64/f2/6730ace05583dbca7c1b406d59f4266e48cd0d360566e71482420fb849fc/orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45", size = 134572, upload-time = "2025-08-12T15:11:18.205Z" },
{ url = "https://files.pythonhosted.org/packages/96/0f/7d3e03a30d5aac0432882b539a65b8c02cb6dd4221ddb893babf09c424cc/orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e", size = 123869, upload-time = "2025-08-12T15:11:19.554Z" },
{ url = "https://files.pythonhosted.org/packages/45/80/1513265eba6d4a960f078f4b1d2bff94a571ab2d28c6f9835e03dfc65cc6/orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e", size = 124430, upload-time = "2025-08-12T15:11:20.914Z" },
{ url = "https://files.pythonhosted.org/packages/fb/61/eadf057b68a332351eeb3d89a4cc538d14f31cd8b5ec1b31a280426ccca2/orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732", size = 119598, upload-time = "2025-08-12T15:11:22.372Z" },
{ url = "https://files.pythonhosted.org/packages/6b/3f/7f4b783402143d965ab7e9a2fc116fdb887fe53bdce7d3523271cd106098/orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36", size = 114052, upload-time = "2025-08-12T15:11:23.762Z" },
{ url = "https://files.pythonhosted.org/packages/c2/f3/0dd6b4750eb556ae4e2c6a9cb3e219ec642e9c6d95f8ebe5dc9020c67204/orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219", size = 226419, upload-time = "2025-08-12T15:11:25.517Z" },
{ url = "https://files.pythonhosted.org/packages/44/d5/e67f36277f78f2af8a4690e0c54da6b34169812f807fd1b4bfc4dbcf9558/orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad", size = 115803, upload-time = "2025-08-12T15:11:27.357Z" },
{ url = "https://files.pythonhosted.org/packages/24/37/ff8bc86e0dacc48f07c2b6e20852f230bf4435611bab65e3feae2b61f0ae/orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2", size = 111337, upload-time = "2025-08-12T15:11:28.805Z" },
{ url = "https://files.pythonhosted.org/packages/b9/25/37d4d3e8079ea9784ea1625029988e7f4594ce50d4738b0c1e2bf4a9e201/orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe", size = 116222, upload-time = "2025-08-12T15:11:30.18Z" },
{ url = "https://files.pythonhosted.org/packages/b7/32/a63fd9c07fce3b4193dcc1afced5dd4b0f3a24e27556604e9482b32189c9/orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae", size = 119020, upload-time = "2025-08-12T15:11:31.59Z" },
{ url = "https://files.pythonhosted.org/packages/b4/b6/400792b8adc3079a6b5d649264a3224d6342436d9fac9a0ed4abc9dc4596/orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6", size = 120721, upload-time = "2025-08-12T15:11:33.035Z" },
{ url = "https://files.pythonhosted.org/packages/40/f3/31ab8f8c699eb9e65af8907889a0b7fef74c1d2b23832719a35da7bb0c58/orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1", size = 123574, upload-time = "2025-08-12T15:11:34.433Z" },
{ url = "https://files.pythonhosted.org/packages/bd/a6/ce4287c412dff81878f38d06d2c80845709c60012ca8daf861cb064b4574/orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa", size = 121225, upload-time = "2025-08-12T15:11:36.133Z" },
{ url = "https://files.pythonhosted.org/packages/69/b0/7a881b2aef4fed0287d2a4fbb029d01ed84fa52b4a68da82bdee5e50598e/orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e", size = 119201, upload-time = "2025-08-12T15:11:37.642Z" },
{ url = "https://files.pythonhosted.org/packages/cf/98/a325726b37f7512ed6338e5e65035c3c6505f4e628b09a5daf0419f054ea/orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15", size = 392193, upload-time = "2025-08-12T15:11:39.153Z" },
{ url = "https://files.pythonhosted.org/packages/cb/4f/a7194f98b0ce1d28190e0c4caa6d091a3fc8d0107ad2209f75c8ba398984/orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac", size = 134548, upload-time = "2025-08-12T15:11:40.768Z" },
{ url = "https://files.pythonhosted.org/packages/e8/5e/b84caa2986c3f472dc56343ddb0167797a708a8d5c3be043e1e2677b55df/orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8", size = 123798, upload-time = "2025-08-12T15:11:42.164Z" },
{ url = "https://files.pythonhosted.org/packages/9c/5b/e398449080ce6b4c8fcadad57e51fa16f65768e1b142ba90b23ac5d10801/orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5", size = 124402, upload-time = "2025-08-12T15:11:44.036Z" },
{ url = "https://files.pythonhosted.org/packages/b3/66/429e4608e124debfc4790bfc37131f6958e59510ba3b542d5fc163be8e5f/orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d", size = 119498, upload-time = "2025-08-12T15:11:45.864Z" },
{ url = "https://files.pythonhosted.org/packages/7b/04/f8b5f317cce7ad3580a9ad12d7e2df0714dfa8a83328ecddd367af802f5b/orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535", size = 114051, upload-time = "2025-08-12T15:11:47.555Z" },
{ url = "https://files.pythonhosted.org/packages/cd/8b/360674cd817faef32e49276187922a946468579fcaf37afdfb6c07046e92/orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f", size = 238238, upload-time = "2025-08-26T17:44:54.214Z" },
{ url = "https://files.pythonhosted.org/packages/05/3d/5fa9ea4b34c1a13be7d9046ba98d06e6feb1d8853718992954ab59d16625/orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91", size = 127713, upload-time = "2025-08-26T17:44:55.596Z" },
{ url = "https://files.pythonhosted.org/packages/e5/5f/e18367823925e00b1feec867ff5f040055892fc474bf5f7875649ecfa586/orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904", size = 123241, upload-time = "2025-08-26T17:44:57.185Z" },
{ url = "https://files.pythonhosted.org/packages/0f/bd/3c66b91c4564759cf9f473251ac1650e446c7ba92a7c0f9f56ed54f9f0e6/orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6", size = 127895, upload-time = "2025-08-26T17:44:58.349Z" },
{ url = "https://files.pythonhosted.org/packages/82/b5/dc8dcd609db4766e2967a85f63296c59d4722b39503e5b0bf7fd340d387f/orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d", size = 130303, upload-time = "2025-08-26T17:44:59.491Z" },
{ url = "https://files.pythonhosted.org/packages/48/c2/d58ec5fd1270b2aa44c862171891adc2e1241bd7dab26c8f46eb97c6c6f1/orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038", size = 132366, upload-time = "2025-08-26T17:45:00.654Z" },
{ url = "https://files.pythonhosted.org/packages/73/87/0ef7e22eb8dd1ef940bfe3b9e441db519e692d62ed1aae365406a16d23d0/orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb", size = 135180, upload-time = "2025-08-26T17:45:02.424Z" },
{ url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2", size = 132741, upload-time = "2025-08-26T17:45:03.663Z" },
{ url = "https://files.pythonhosted.org/packages/bd/0c/4577fd860b6386ffaa56440e792af01c7882b56d2766f55384b5b0e9d39b/orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55", size = 131104, upload-time = "2025-08-26T17:45:04.939Z" },
{ url = "https://files.pythonhosted.org/packages/66/4b/83e92b2d67e86d1c33f2ea9411742a714a26de63641b082bdbf3d8e481af/orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1", size = 403887, upload-time = "2025-08-26T17:45:06.228Z" },
{ url = "https://files.pythonhosted.org/packages/6d/e5/9eea6a14e9b5ceb4a271a1fd2e1dec5f2f686755c0fab6673dc6ff3433f4/orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824", size = 145855, upload-time = "2025-08-26T17:45:08.338Z" },
{ url = "https://files.pythonhosted.org/packages/45/78/8d4f5ad0c80ba9bf8ac4d0fc71f93a7d0dc0844989e645e2074af376c307/orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f", size = 135361, upload-time = "2025-08-26T17:45:09.625Z" },
{ url = "https://files.pythonhosted.org/packages/0b/5f/16386970370178d7a9b438517ea3d704efcf163d286422bae3b37b88dbb5/orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204", size = 136190, upload-time = "2025-08-26T17:45:10.962Z" },
{ url = "https://files.pythonhosted.org/packages/09/60/db16c6f7a41dd8ac9fb651f66701ff2aeb499ad9ebc15853a26c7c152448/orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b", size = 131389, upload-time = "2025-08-26T17:45:12.285Z" },
{ url = "https://files.pythonhosted.org/packages/3e/2a/bb811ad336667041dea9b8565c7c9faf2f59b47eb5ab680315eea612ef2e/orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e", size = 126120, upload-time = "2025-08-26T17:45:13.515Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" },
{ url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" },
{ url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" },
{ url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" },
{ url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" },
{ url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" },
{ url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" },
{ url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" },
{ url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" },
{ url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" },
{ url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" },
{ url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" },
{ url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" },
{ url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" },
{ url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" },
{ url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" },
{ url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" },
{ url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" },
{ url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" },
{ url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" },
{ url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" },
{ url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" },
{ url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" },
{ url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" },
{ url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" },
{ url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" },
{ url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" },
{ url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" },
{ url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" },
{ url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" },
]
[[package]]
@@ -3914,11 +3975,11 @@ wheels = [
[[package]]
name = "platformdirs"
version = "4.3.8"
version = "4.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
{ url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
]
[[package]]
@@ -4513,6 +4574,43 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" },
]
[[package]]
name = "pytest-json-report"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
{ name = "pytest-metadata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4f/d3/765dae9712fcd68d820338908c1337e077d5fdadccd5cacf95b9b0bea278/pytest-json-report-1.5.0.tar.gz", hash = "sha256:2dde3c647851a19b5f3700729e8310a6e66efb2077d674f27ddea3d34dc615de", size = 21241, upload-time = "2022-03-15T21:03:10.2Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/81/35/d07400c715bf8a88aa0c1ee9c9eb6050ca7fe5b39981f0eea773feeb0681/pytest_json_report-1.5.0-py3-none-any.whl", hash = "sha256:9897b68c910b12a2e48dd849f9a284b2c79a732a8a9cb398452ddd23d3c8c325", size = 13222, upload-time = "2022-03-15T21:03:08.65Z" },
]
[[package]]
name = "pytest-metadata"
version = "3.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" },
]
[[package]]
name = "pytest-mock"
version = "3.14.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" },
]
[[package]]
name = "pytest-order"
version = "1.3.0"
@@ -5349,6 +5447,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "tornado"
version = "6.5.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" },
{ url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" },
{ url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" },
{ url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" },
{ url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" },
{ url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" },
{ url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" },
{ url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" },
{ url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" },
{ url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" },
{ url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" },
]
[[package]]
name = "tqdm"
version = "4.67.1"