Compare commits

..
48 changed files with 1537 additions and 3544 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ def main():
tree = ast.parse(file.read())
classes = find_classes(tree)
def is_sync(class_spec: Tuple[str, List[str]]) -> bool:
return class_spec[0].startswith("Sync")
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run:
working-directory: libs/cli
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Get changed files
id: changed-files
uses: Ana06/get-changed-files@v2.3.0
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- "3.12"
name: "lint #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Get changed files
id: changed-files
uses: Ana06/get-changed-files@v2.3.0
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
name: "test #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v7
with:
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
working-directory: libs/langgraph
name: "test #${{ matrix.python-version }}"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v7
with:
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
version: ${{ steps.check-version.outputs.version }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python $${ env.PYTHON_VERSION }}
uses: astral-sh/setup-uv@v7
@@ -74,7 +74,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/download-artifact@v6
with:
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
run:
working-directory: libs/langgraph
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- run: SHA=$(git rev-parse HEAD) && echo "SHA=$SHA" >> $GITHUB_ENV
- name: Set up Python 3.11
uses: astral-sh/setup-uv@v7
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
run:
working-directory: libs/langgraph
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- id: files
name: Get changed files
uses: Ana06/get-changed-files@v2.3.0
+3 -3
View File
@@ -27,7 +27,7 @@ jobs:
python: ${{ steps.filter.outputs.python }}
deps: ${{ steps.filter.outputs.deps }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
@@ -100,7 +100,7 @@ jobs:
name: "Check SDK methods matching"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
@@ -118,7 +118,7 @@ jobs:
python-version:
- "3.11"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: astral-sh/setup-uv@v7
with:
+1 -1
View File
@@ -21,7 +21,7 @@
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Install Dependencies
run: |
+72
View File
@@ -0,0 +1,72 @@
name: Deploy Docs Redirects
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
defaults:
run:
working-directory: docs
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 10 # Job will be cancelled if it runs for more than 10 minutes
env:
GITHUB_TOKEN: ${{ secrets.MKDOCS_GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Python
uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
enable-cache: true
cache-suffix: "docs"
- name: Install dependencies
run: |
yarn
uv sync --all-groups
# we run this installation only for internal PRs
# as GITHUB_TOKEN is not available for PRs from outside contributors
if [ -n "${GITHUB_TOKEN}" ]; then
uv run pip install "git+https://${GITHUB_TOKEN}@github.com/langchain-ai/mkdocs-material-insiders.git"
fi
- name: Build llms-text
run: make llms-text
- name: Build site (redirects only)
run: make build-docs
env:
OPENAI_API_KEY: sf-proj-1234567890 # fake placeholder, shouldn't actually be used
ANTHROPIC_API_KEY: sk-ant-api03-1234567890 # fake placeholder, shouldn't actually be used
- name: Configure GitHub Pages
if: github.ref == 'refs/heads/main'
uses: actions/configure-pages@v5
- name: Upload Pages Artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v4
with:
path: ./docs/site/
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
id: deployment
uses: actions/deploy-pages@v4
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 1
+5 -6
View File
@@ -25,7 +25,7 @@ jobs:
tag: ${{ steps.check-version.outputs.tag }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python
uses: astral-sh/setup-uv@v7
@@ -86,7 +86,7 @@ jobs:
outputs:
release-body: ${{ steps.generate-release-body.outputs.release-body }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
repository: langchain-ai/langgraph
path: langgraph
@@ -157,7 +157,7 @@ jobs:
- test-pypi-publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
# We explicitly *don't* set up caching here. This ensures our tests are
# maximally sensitive to catching breakage.
@@ -260,7 +260,7 @@ jobs:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python
uses: astral-sh/setup-uv@v7
@@ -301,7 +301,7 @@ jobs:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python
uses: astral-sh/setup-uv@v7
@@ -322,6 +322,5 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
generateReleaseNotes: false
tag: ${{needs.build.outputs.tag}}
name: ${{ needs.build.outputs.pkg-name }}==${{ needs.build.outputs.version }}
body: ${{ needs.release-notes.outputs.release-body }}
commit: ${{ github.sha }}
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- "latest"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up Python + Poetry
uses: astral-sh/setup-uv@v7
with:
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Set up uv
uses: astral-sh/setup-uv@v7
+1 -1
View File
@@ -67,7 +67,7 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
- [LangChain](https://docs.langchain.com/oss/python/langchain/overview) Provides integrations and composable components to streamline LLM application development.
> [!NOTE]
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://docs.langchain.com/oss/javascript/langgraph/overview).
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
## Additional resources
@@ -2340,13 +2340,13 @@ __metadata:
linkType: hard
"js-yaml@npm:^4.1.0":
version: 4.1.1
resolution: "js-yaml@npm:4.1.1"
version: 4.1.0
resolution: "js-yaml@npm:4.1.0"
dependencies:
argparse: "npm:^2.0.1"
bin:
js-yaml: bin/js-yaml.js
checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7
checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f
languageName: node
linkType: hard
@@ -97,8 +97,7 @@ class InMemorySaver(
self.stack.enter_context(self.blobs) # type: ignore[arg-type]
def __enter__(self) -> InMemorySaver:
self.stack.__enter__()
return self
return self.stack.__enter__()
def __exit__(
self,
@@ -109,8 +108,7 @@ class InMemorySaver(
return self.stack.__exit__(exc_type, exc_value, traceback)
async def __aenter__(self) -> InMemorySaver:
self.stack.__enter__()
return self
return self.stack.__enter__()
async def __aexit__(
self,
@@ -15,7 +15,9 @@ class UntypedSerializerProtocol(Protocol):
class SerializerProtocol(Protocol):
"""Protocol for serialization and deserialization of objects.
- `dumps`: Serialize an object to bytes.
- `dumps_typed`: Serialize an object to a tuple `(type, bytes)`.
- `loads`: Deserialize an object from bytes.
- `loads_typed`: Deserialize an object from a tuple `(type, bytes)`.
Valid implementations include the `pickle`, `json` and `orjson` modules.
+2 -9
View File
@@ -188,14 +188,7 @@ class TestMemorySaver:
assert len(search_results_4) == 0
async def test_memory_saver() -> None:
def test_memory_saver() -> None:
from langgraph.checkpoint.memory import InMemorySaver
memory_saver = InMemorySaver()
assert isinstance(memory_saver, InMemorySaver)
async with memory_saver as async_memory_saver:
assert async_memory_saver is memory_saver
with memory_saver as sync_memory_saver:
assert sync_memory_saver is memory_saver
assert isinstance(InMemorySaver(), InMemorySaver)
-2
View File
@@ -15,7 +15,6 @@ import msgspec
from langgraph_cli.schemas import (
AuthConfig,
CacheConfig,
CheckpointerConfig,
Config,
ConfigurableHeaderConfig,
@@ -112,7 +111,6 @@ def add_descriptions_to_schema(schema, cls):
SecurityConfig,
HttpConfig,
CorsConfig,
CacheConfig,
ThreadTTLConfig,
CheckpointerConfig,
SerdeConfig,
-60
View File
@@ -154,7 +154,6 @@ def validate_config(config: Config) -> Config:
"env": config.get("env", {}),
"store": config.get("store"),
"auth": config.get("auth"),
"encryption": config.get("encryption"),
"http": config.get("http"),
"checkpointer": config.get("checkpointer"),
"ui": config.get("ui"),
@@ -229,14 +228,6 @@ def validate_config(config: Config) -> Config:
f"Invalid auth.path format: '{auth_conf['path']}'. "
"Must be in format './path/to/file.py:attribute_name'"
)
# Validate encryption config
if encryption_conf := config.get("encryption"):
if "path" in encryption_conf:
if ":" not in encryption_conf["path"]:
raise ValueError(
f"Invalid encryption.path format: '{encryption_conf['path']}'. "
"Must be in format './path/to/file.py:attribute_name'"
)
if http_conf := config.get("http"):
if "app" in http_conf:
if ":" not in http_conf["app"]:
@@ -623,49 +614,6 @@ def _update_auth_path(
)
def _update_encryption_path(
config_path: pathlib.Path, config: Config, local_deps: LocalDeps
) -> None:
"""Update encryption.path to use Docker container paths."""
encryption_conf = config.get("encryption")
if not encryption_conf or not (path_str := encryption_conf.get("path")):
return
module_str, sep, attr_str = path_str.partition(":")
if not sep or not module_str.startswith("."):
return # Already validated or absolute path
resolved = config_path.parent / module_str
if not resolved.exists():
raise FileNotFoundError(
f"Encryption file not found: {resolved} (from {path_str})"
)
if not resolved.is_file():
raise IsADirectoryError(f"Encryption path must be a file: {resolved}")
# Check faux packages first (higher priority)
for faux_path, (_, destpath) in local_deps.faux_pkgs.items():
if resolved.is_relative_to(faux_path):
new_path = f"{destpath}/{resolved.relative_to(faux_path)}:{attr_str}"
encryption_conf["path"] = new_path
return
# Check real packages
for real_path in local_deps.real_pkgs:
if resolved.is_relative_to(real_path):
new_path = (
f"/deps/{real_path.name}/{resolved.relative_to(real_path)}:{attr_str}"
)
encryption_conf["path"] = new_path
return
raise ValueError(
f"Encryption file '{resolved}' not covered by dependencies.\n"
"Add its parent directory to the 'dependencies' array in your config.\n"
f"Current dependencies: {config['dependencies']}"
)
def _update_http_app_path(
config_path: pathlib.Path, config: Config, local_deps: LocalDeps
) -> None:
@@ -861,8 +809,6 @@ def python_config_to_docker(
_update_graph_paths(config_path, config, local_deps)
# Rewrite auth path, so it points to the correct location in the Docker container
_update_auth_path(config_path, config, local_deps)
# Rewrite encryption path, so it points to the correct location in the Docker container
_update_encryption_path(config_path, config, local_deps)
# Rewrite HTTP app path, so it points to the correct location in the Docker container
_update_http_app_path(config_path, config, local_deps)
@@ -953,9 +899,6 @@ ADD {relpath} /deps/{name}
if (auth_config := config.get("auth")) is not None:
env_vars.append(f"ENV LANGGRAPH_AUTH='{json.dumps(auth_config)}'")
if (encryption_config := config.get("encryption")) is not None:
env_vars.append(f"ENV LANGGRAPH_ENCRYPTION='{json.dumps(encryption_config)}'")
if (http_config := config.get("http")) is not None:
env_vars.append(f"ENV LANGGRAPH_HTTP='{json.dumps(http_config)}'")
@@ -1079,9 +1022,6 @@ def node_config_to_docker(
if (auth_config := config.get("auth")) is not None:
env_vars.append(f"ENV LANGGRAPH_AUTH='{json.dumps(auth_config)}'")
if (encryption_config := config.get("encryption")) is not None:
env_vars.append(f"ENV LANGGRAPH_ENCRYPTION='{json.dumps(encryption_config)}'")
if (http_config := config.get("http")) is not None:
env_vars.append(f"ENV LANGGRAPH_HTTP='{json.dumps(http_config)}'")
+2 -83
View File
@@ -233,27 +233,6 @@ class SecurityConfig(TypedDict, total=False):
"""
class CacheConfig(TypedDict, total=False):
cache_keys: list[str]
"""Optional. List of header keys to use for caching.
Example:
["user_id", "workspace_id"]
"""
ttl_seconds: int
"""Optional. Time-to-live in seconds for cached items.
Example:
3600
"""
max_size: int
"""Optional. Maximum size of the cache.
Example:
100
"""
class AuthConfig(TypedDict, total=False):
"""Configuration for custom authentication logic and how it integrates into the OpenAPI spec."""
@@ -290,37 +269,6 @@ class AuthConfig(TypedDict, total=False):
]
}
"""
cache: CacheConfig
"""Optional. Cache configuration for the server.
Example:
{
"cache_keys": ["user_id", "workspace_id"],
"ttl_seconds": 3600,
"max_size": 100
}
"""
class EncryptionConfig(TypedDict, total=False):
"""Configuration for custom at-rest encryption logic.
Allows you to implement custom encryption for sensitive data stored in the database,
including metadata fields and checkpoint blobs.
"""
path: str
"""Required. Path to an instance of the Encryption() class that implements custom encryption handlers.
Format: "path/to/file.py:my_encryption"
Example:
{
"encryption": {
"path": "./encryption.py:my_encryption"
}
}
"""
class CorsConfig(TypedDict, total=False):
@@ -419,12 +367,7 @@ class HttpConfig(TypedDict, total=False):
Default is False.
"""
disable_mcp: bool
"""Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.
Default is False.
"""
disable_a2a: bool
"""Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.
"""Optional. If `True`, /mcp routes are removed, disabling the MCP server.
Default is False.
"""
@@ -434,17 +377,6 @@ class HttpConfig(TypedDict, total=False):
Set to True to disable the following endpoints: /openapi.json, /info, /metrics, /docs.
This will also make the /ok endpoint skip any DB or other checks, always returning {"ok": True}.
Default is False.
"""
disable_ui: bool
"""Optional. If `True`, /ui routes are removed, disabling the UI server.
Default is False.
"""
disable_webhooks: bool
"""Optional. If `True`, webhooks are disabled. Runs created with an associated webhook will
still be executed, but the webhook event will not be sent.
Default is False.
"""
cors: CorsConfig | None
@@ -477,12 +409,6 @@ class HttpConfig(TypedDict, total=False):
Default is False. This flag only affects authentication behavior
if `app` is provided and contains custom routes.
"""
mount_prefix: str
"""Optional. URL prefix to prepend to all the routes.
Example:
"/api"
"""
class Config(TypedDict, total=False):
@@ -598,16 +524,10 @@ class Config(TypedDict, total=False):
"""
auth: AuthConfig | None
"""Optional. Custom authentication config, including the path to your Python auth logic and
"""Optional. Custom authentication config, including the path to your Python auth logic and
the OpenAPI security definitions it uses.
"""
encryption: EncryptionConfig | None
"""Optional. Custom at-rest encryption config, including the path to your Python encryption logic.
Allows you to implement custom encryption for sensitive data stored in the database.
"""
http: HttpConfig | None
"""Optional. Configuration for the built-in HTTP server, controlling which custom routes are exposed
and how cross-origin requests are handled.
@@ -630,7 +550,6 @@ __all__ = [
"StoreConfig",
"CheckpointerConfig",
"AuthConfig",
"EncryptionConfig",
"HttpConfig",
"MiddlewareOrders",
"Distros",
+1 -77
View File
@@ -99,17 +99,6 @@
},
"description": "Optional. Additional Docker instructions that will be appended to your base Dockerfile.\n\nUseful for installing OS packages, setting environment variables, etc."
},
"encryption": {
"anyOf": [
{
"$ref": "#/$defs/EncryptionConfig"
},
{
"type": "null"
}
],
"description": "Optional. Custom at-rest encryption config, including the path to your Python encryption logic.\n\nAllows you to implement custom encryption for sensitive data stored in the database.\n"
},
"env": {
"anyOf": [
{
@@ -303,17 +292,6 @@
},
"description": "Optional. Additional Docker instructions that will be appended to your base Dockerfile.\n\nUseful for installing OS packages, setting environment variables, etc."
},
"encryption": {
"anyOf": [
{
"$ref": "#/$defs/EncryptionConfig"
},
{
"type": "null"
}
],
"description": "Optional. Custom at-rest encryption config, including the path to your Python encryption logic.\n\nAllows you to implement custom encryption for sensitive data stored in the database.\n"
},
"env": {
"anyOf": [
{
@@ -427,10 +405,6 @@
"description": "Configuration for custom authentication logic and how it integrates into the OpenAPI spec.",
"type": "object",
"properties": {
"cache": {
"$ref": "#/$defs/CacheConfig",
"description": "Optional. Cache configuration for the server.\n"
},
"disable_studio_auth": {
"type": "boolean",
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
@@ -446,29 +420,6 @@
},
"required": []
},
"CacheConfig": {
"title": "CacheConfig",
"type": "object",
"properties": {
"cache_keys": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional. List of header keys to use for caching.\n"
},
"max_size": {
"type": "integer",
"description": "Optional. Maximum size of the cache.\n"
},
"ttl_seconds": {
"type": "integer",
"description": "Optional. Time-to-live in seconds for cached items.\n"
}
},
"required": [],
"description": "dict() -> new empty dictionary\ndict(mapping) -> new dictionary initialized from a mapping object's\n (key, value) pairs\ndict(iterable) -> new dictionary initialized as if via:\n d = {}\n for k, v in iterable:\n d[k] = v\ndict(**kwargs) -> new dictionary initialized with the name=value pairs\n in the keyword argument list. For example: dict(one=1, two=2)"
},
"SecurityConfig": {
"title": "SecurityConfig",
"description": "Configuration for OpenAPI security definitions and requirements.\n\nUseful for specifying global or path-level authentication and authorization flows\n(e.g., OAuth2, API key headers, etc.).",
@@ -614,17 +565,6 @@
},
"required": []
},
"EncryptionConfig": {
"title": "EncryptionConfig",
"description": "Configuration for custom at-rest encryption logic.\n\n Allows you to implement custom encryption for sensitive data stored in the database,\n including metadata fields and checkpoint blobs.",
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": []
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
@@ -656,17 +596,13 @@
],
"description": "Optional. Defines CORS restrictions. If omitted, no special rules are set and\ncross-origin behavior depends on default server settings.\n"
},
"disable_a2a": {
"type": "boolean",
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
},
"disable_assistants": {
"type": "boolean",
"description": "Optional. If `True`, /assistants routes are removed from the server.\n\nDefault is False (meaning /assistants is enabled).\n"
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /mcp routes are removed, disabling the MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
@@ -684,14 +620,6 @@
"type": "boolean",
"description": "Optional. If `True`, /threads routes are removed.\n\nDefault is False.\n"
},
"disable_ui": {
"type": "boolean",
"description": "Optional. If `True`, /ui routes are removed, disabling the UI server.\n\nDefault is False.\n"
},
"disable_webhooks": {
"type": "boolean",
"description": "Optional. If `True`, webhooks are disabled. Runs created with an associated webhook will\nstill be executed, but the webhook event will not be sent.\n\nDefault is False.\n"
},
"enable_custom_route_auth": {
"type": "boolean",
"description": "Optional. If `True`, authentication is enabled for custom routes,\nnot just the routes that are protected by default.\n(Routes protected by default include /assistants, /threads, and /runs).\n\nDefault is False. This flag only affects authentication behavior\nif `app` is provided and contains custom routes.\n"
@@ -720,10 +648,6 @@
}
],
"description": "Optional. Defines the order in which to apply server customizations.\n"
},
"mount_prefix": {
"type": "string",
"description": "Optional. URL prefix to prepend to all the routes.\n"
}
},
"required": []
+1 -77
View File
@@ -99,17 +99,6 @@
},
"description": "Optional. Additional Docker instructions that will be appended to your base Dockerfile.\n\nUseful for installing OS packages, setting environment variables, etc."
},
"encryption": {
"anyOf": [
{
"$ref": "#/$defs/EncryptionConfig"
},
{
"type": "null"
}
],
"description": "Optional. Custom at-rest encryption config, including the path to your Python encryption logic.\n\nAllows you to implement custom encryption for sensitive data stored in the database.\n"
},
"env": {
"anyOf": [
{
@@ -303,17 +292,6 @@
},
"description": "Optional. Additional Docker instructions that will be appended to your base Dockerfile.\n\nUseful for installing OS packages, setting environment variables, etc."
},
"encryption": {
"anyOf": [
{
"$ref": "#/$defs/EncryptionConfig"
},
{
"type": "null"
}
],
"description": "Optional. Custom at-rest encryption config, including the path to your Python encryption logic.\n\nAllows you to implement custom encryption for sensitive data stored in the database.\n"
},
"env": {
"anyOf": [
{
@@ -427,10 +405,6 @@
"description": "Configuration for custom authentication logic and how it integrates into the OpenAPI spec.",
"type": "object",
"properties": {
"cache": {
"$ref": "#/$defs/CacheConfig",
"description": "Optional. Cache configuration for the server.\n"
},
"disable_studio_auth": {
"type": "boolean",
"description": "Optional. Whether to disable LangSmith API-key authentication for requests originating the Studio.\n\nDefaults to False, meaning that if a particular header is set, the server will verify the `x-api-key` header\nvalue is a valid API key for the deployment's workspace. If `True`, all requests will go through your custom\nauthentication logic, regardless of origin of the request.\n"
@@ -446,29 +420,6 @@
},
"required": []
},
"CacheConfig": {
"title": "CacheConfig",
"type": "object",
"properties": {
"cache_keys": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional. List of header keys to use for caching.\n"
},
"max_size": {
"type": "integer",
"description": "Optional. Maximum size of the cache.\n"
},
"ttl_seconds": {
"type": "integer",
"description": "Optional. Time-to-live in seconds for cached items.\n"
}
},
"required": [],
"description": "dict() -> new empty dictionary\ndict(mapping) -> new dictionary initialized from a mapping object's\n (key, value) pairs\ndict(iterable) -> new dictionary initialized as if via:\n d = {}\n for k, v in iterable:\n d[k] = v\ndict(**kwargs) -> new dictionary initialized with the name=value pairs\n in the keyword argument list. For example: dict(one=1, two=2)"
},
"SecurityConfig": {
"title": "SecurityConfig",
"description": "Configuration for OpenAPI security definitions and requirements.\n\nUseful for specifying global or path-level authentication and authorization flows\n(e.g., OAuth2, API key headers, etc.).",
@@ -614,17 +565,6 @@
},
"required": []
},
"EncryptionConfig": {
"title": "EncryptionConfig",
"description": "Configuration for custom at-rest encryption logic.\n\n Allows you to implement custom encryption for sensitive data stored in the database,\n including metadata fields and checkpoint blobs.",
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": []
},
"HttpConfig": {
"title": "HttpConfig",
"description": "Configuration for the built-in HTTP server that powers your deployment's routes and endpoints.",
@@ -656,17 +596,13 @@
],
"description": "Optional. Defines CORS restrictions. If omitted, no special rules are set and\ncross-origin behavior depends on default server settings.\n"
},
"disable_a2a": {
"type": "boolean",
"description": "Optional. If `True`, /a2a routes are removed, disabling default support to expose the deployment as an agent-to-agent (A2A) server.\n\nDefault is False.\n"
},
"disable_assistants": {
"type": "boolean",
"description": "Optional. If `True`, /assistants routes are removed from the server.\n\nDefault is False (meaning /assistants is enabled).\n"
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If `True`, /mcp routes are removed, disabling default support to expose the deployment as an MCP server.\n\nDefault is False.\n"
"description": "Optional. If `True`, /mcp routes are removed, disabling the MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
@@ -684,14 +620,6 @@
"type": "boolean",
"description": "Optional. If `True`, /threads routes are removed.\n\nDefault is False.\n"
},
"disable_ui": {
"type": "boolean",
"description": "Optional. If `True`, /ui routes are removed, disabling the UI server.\n\nDefault is False.\n"
},
"disable_webhooks": {
"type": "boolean",
"description": "Optional. If `True`, webhooks are disabled. Runs created with an associated webhook will\nstill be executed, but the webhook event will not be sent.\n\nDefault is False.\n"
},
"enable_custom_route_auth": {
"type": "boolean",
"description": "Optional. If `True`, authentication is enabled for custom routes,\nnot just the routes that are protected by default.\n(Routes protected by default include /assistants, /threads, and /runs).\n\nDefault is False. This flag only affects authentication behavior\nif `app` is provided and contains custom routes.\n"
@@ -720,10 +648,6 @@
}
],
"description": "Optional. Defines the order in which to apply server customizations.\n"
},
"mount_prefix": {
"type": "string",
"description": "Optional. URL prefix to prepend to all the routes.\n"
}
},
"required": []
-53
View File
@@ -48,7 +48,6 @@ def test_validate_config():
"env": {},
"store": None,
"auth": None,
"encryption": None,
"checkpointer": None,
"http": None,
"ui": None,
@@ -75,7 +74,6 @@ def test_validate_config():
"env": env,
"store": None,
"auth": None,
"encryption": None,
"checkpointer": None,
"http": None,
"ui": None,
@@ -750,57 +748,6 @@ RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not foun
assert additional_contexts == {}
def test_config_to_docker_python_encryption():
# Test that encryption config is included in validation
graphs = {"agent": "./agent.py:graph"}
validated = validate_config(
{
"python_version": "3.11",
"graphs": graphs,
"dependencies": ["."],
"encryption": {"path": "./encryption.py:encryption"},
}
)
# Verify that encryption config is preserved after validation
assert validated.get("encryption") is not None
assert validated["encryption"]["path"] == "./encryption.py:encryption"
def test_config_to_docker_python_encryption_bad_path():
# Test that invalid encryption path format raises ValueError
graphs = {"agent": "./agent.py:graph"}
with pytest.raises(ValueError, match="Invalid encryption.path format"):
validate_config(
{
"python_version": "3.11",
"graphs": graphs,
"dependencies": ["."],
"encryption": {"path": "./encryption.py"}, # Missing :attribute
}
)
def test_config_to_docker_python_encryption_formatted():
# Test that encryption config is properly formatted in Docker output
graphs = {"agent": "./graphs/agent.py:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
PATH_TO_CONFIG,
validate_config(
{
"python_version": "3.11",
"dependencies": ["."],
"graphs": graphs,
"encryption": {"path": "./agent.py:my_encryption"},
}
),
"langchain/langgraph-api",
)
# Verify that LANGGRAPH_ENCRYPTION is in the docker output with the correct path
assert "LANGGRAPH_ENCRYPTION=" in actual_docker_stdin
assert "/deps/outer-unit_tests/unit_tests/agent.py:my_encryption" in actual_docker_stdin
def test_config_to_docker_nodejs_internal_docker_tag():
graphs = {"agent": "./graphs/agent.js:graph"}
actual_docker_stdin, additional_contexts = config_to_docker(
+1 -1
View File
@@ -67,7 +67,7 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
- [LangChain](https://docs.langchain.com/oss/python/langchain/overview) Provides integrations and composable components to streamline LLM application development.
> [!NOTE]
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://docs.langchain.com/oss/javascript/langgraph/overview).
> Looking for the JS version of LangGraph? See the [JS repo](https://github.com/langchain-ai/langgraphjs) and the [JS docs](https://langchain-ai.github.io/langgraphjs/).
## Additional resources
+2 -4
View File
@@ -725,10 +725,9 @@ class RemoteGraph(PregelProtocol):
input = None
else:
command = None
thread_id = sanitized_config.get("configurable", {}).pop("thread_id", None)
for chunk in sync_client.runs.stream(
thread_id=thread_id,
thread_id=sanitized_config["configurable"].get("thread_id"),
assistant_id=self.assistant_id,
input=input,
command=command,
@@ -835,10 +834,9 @@ class RemoteGraph(PregelProtocol):
input = None
else:
command = None
thread_id = sanitized_config.get("configurable", {}).pop("thread_id", None)
async for chunk in client.runs.stream(
thread_id=thread_id,
thread_id=sanitized_config["configurable"].get("thread_id"),
assistant_id=self.assistant_id,
input=input,
command=command,
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.0.4"
version = "1.0.3"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -3271,7 +3271,7 @@ def test_root_graph(
content="result for query",
name="search_api",
tool_call_id="tool_call123",
id="00000000-0000-4000-8000-000000000004",
id="00000000-0000-4000-8000-000000000024",
)
]
},
@@ -3294,7 +3294,7 @@ def test_root_graph(
content="result for another",
name="search_api",
tool_call_id="tool_call456",
id="00000000-0000-4000-8000-000000000005",
id="00000000-0000-4000-8000-000000000030",
)
]
},
@@ -3898,7 +3898,7 @@ def test_root_graph(
"__root__": [
HumanMessage(
content="what is weather in sf",
id="00000000-0000-4000-8000-000000000008",
id="00000000-0000-4000-8000-000000000051",
),
AIMessage(
content="",
@@ -3918,7 +3918,7 @@ def test_root_graph(
),
AIMessage(content="answer", id="ai2"),
AIMessage(
content="an extra message", id="00000000-0000-4000-8000-000000000010"
content="an extra message", id="00000000-0000-4000-8000-000000000066"
),
HumanMessage(content="what is weather in la"),
],
+2 -42
View File
@@ -840,46 +840,6 @@ def test_invoke():
assert result == {"messages": [{"type": "human", "content": "world"}]}
def test_invoke_sanitizes_thread_id():
# Ensure that invoking with thread_id passes thread_id as a top-level arg
# and removes it from the config body.
mock_sync_client = MagicMock()
mock_sync_client.runs.stream.return_value = []
remote_pregel = RemoteGraph("test_graph_id", sync_client=mock_sync_client)
config = {"configurable": {"thread_id": "thread_1"}}
remote_pregel.invoke(
{"input": {"messages": [{"type": "human", "content": "hello"}]}}, config
)
assert mock_sync_client.runs.stream.called
_, kwargs = mock_sync_client.runs.stream.call_args
assert kwargs.get("thread_id") == "thread_1"
passed_config = kwargs.get("config") or {}
assert "configurable" in passed_config
assert "thread_id" not in passed_config["configurable"]
assert not passed_config["configurable"]
def test_stream_sanitizes_thread_id():
# Ensure that streaming with thread_id passes thread_id as a top-level arg
# and removes it from the config body.
mock_sync_client = MagicMock()
mock_sync_client.runs.stream.return_value = []
remote_pregel = RemoteGraph("test_graph_id", sync_client=mock_sync_client)
config = {"configurable": {"thread_id": "thread_2"}}
list(remote_pregel.stream({"input": {"messages": []}}, config))
assert mock_sync_client.runs.stream.called
_, kwargs = mock_sync_client.runs.stream.call_args
assert kwargs.get("thread_id") == "thread_2"
passed_config = kwargs.get("config") or {}
assert "configurable" in passed_config
assert "thread_id" not in passed_config["configurable"]
assert not passed_config["configurable"]
@pytest.mark.anyio
async def test_ainvoke():
# set up test
@@ -919,8 +879,8 @@ async def test_langgraph_cloud_integration():
from langgraph.graph import END, START, MessagesState, StateGraph
# create RemotePregel instance
client = get_client(url="http://localhost:8123")
sync_client = get_sync_client(url="http://localhost:8123")
client = get_client()
sync_client = get_sync_client()
remote_pregel = RemoteGraph(
"agent",
client=client,
+780 -895
View File
File diff suppressed because it is too large Load Diff
+487 -684
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,7 +17,7 @@ lint lint_diff:
uv run ruff check .
[ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
uv run ty check .
uvx ty check .
format format_diff:
uv run ruff check --select I --fix $(PYTHON_FILES)
+2 -4
View File
@@ -1,8 +1,6 @@
from langgraph_sdk.auth import Auth
from langgraph_sdk.client import get_client, get_sync_client
from langgraph_sdk.encryption import Encryption
from langgraph_sdk.encryption.types import EncryptionContext
__version__ = "0.2.14"
__version__ = "0.2.10"
__all__ = ["Auth", "Encryption", "EncryptionContext", "get_client", "get_sync_client"]
__all__ = ["Auth", "get_client", "get_sync_client"]
+73 -241
View File
@@ -35,7 +35,6 @@ from langgraph_sdk.schema import (
Assistant,
AssistantSelectField,
AssistantSortBy,
AssistantsSearchResponse,
AssistantVersion,
CancelAction,
Checkpoint,
@@ -49,7 +48,6 @@ from langgraph_sdk.schema import (
Durability,
GraphSchema,
IfNotExists,
Input,
Item,
Json,
ListNamespaceResponse,
@@ -82,37 +80,25 @@ logger = logging.getLogger(__name__)
RESERVED_HEADERS = ("x-api-key",)
NOT_PROVIDED = cast(None, object())
def _get_api_key(api_key: str | None = NOT_PROVIDED) -> str | None:
def _get_api_key(api_key: str | None = None) -> str | None:
"""Get the API key from the environment.
Precedence:
1. explicit string argument
2. LANGGRAPH_API_KEY (if api_key not provided)
3. LANGSMITH_API_KEY (if api_key not provided)
4. LANGCHAIN_API_KEY (if api_key not provided)
Args:
api_key: The API key to use. Can be:
- A string: use this exact API key
- None: explicitly skip loading from environment
- NOT_PROVIDED (default): auto-load from environment variables
1. explicit argument
2. LANGGRAPH_API_KEY
3. LANGSMITH_API_KEY
4. LANGCHAIN_API_KEY
"""
if isinstance(api_key, str):
if api_key:
return api_key
if api_key is NOT_PROVIDED:
# api_key is not explicitly provided, try to load from environment
for prefix in ["LANGGRAPH", "LANGSMITH", "LANGCHAIN"]:
if env := os.getenv(f"{prefix}_API_KEY"):
return env.strip().strip('"').strip("'")
# api_key is explicitly None, don't load from environment
return None
for prefix in ["LANGGRAPH", "LANGSMITH", "LANGCHAIN"]:
if env := os.getenv(f"{prefix}_API_KEY"):
return env.strip().strip('"').strip("'")
return None # type: ignore
def _get_headers(
api_key: str | None,
custom_headers: Mapping[str, str] | None,
api_key: str | None, custom_headers: Mapping[str, str] | None
) -> dict[str, str]:
"""Combine api_key and custom user-provided headers."""
custom_headers = custom_headers or {}
@@ -124,9 +110,9 @@ def _get_headers(
"User-Agent": f"langgraph-sdk-py/{langgraph_sdk.__version__}",
**custom_headers,
}
resolved_api_key = _get_api_key(api_key)
if resolved_api_key:
headers["x-api-key"] = resolved_api_key
api_key = _get_api_key(api_key)
if api_key:
headers["x-api-key"] = api_key
return headers
@@ -177,7 +163,7 @@ def _get_run_metadata_from_response(
def get_client(
*,
url: str | None = None,
api_key: str | None = NOT_PROVIDED,
api_key: str | None = None,
headers: Mapping[str, str] | None = None,
timeout: TimeoutTypes | None = None,
) -> LangGraphClient:
@@ -190,16 +176,14 @@ def get_client(
url:
Base URL of the LangGraph API.
- If `None`, the client first attempts an in-process connection via ASGI transport.
If that fails, it defers registration until after app initialization. This
only works if the client is used from within the Agent server.
If that fails, it falls back to `http://localhost:8123`.
api_key:
API key for authentication. Can be:
- A string: use this exact API key
- `None`: explicitly skip loading from environment variables
- Not provided (default): auto-load from environment in this order:
1. `LANGGRAPH_API_KEY`
2. `LANGSMITH_API_KEY`
3. `LANGCHAIN_API_KEY`
API key for authentication. If omitted, the client reads from environment
variables in the following order:
1. Function argument
2. `LANGGRAPH_API_KEY`
3. `LANGSMITH_API_KEY`
4. `LANGCHAIN_API_KEY`
headers:
Additional HTTP headers to include in requests. Merged with authentication headers.
timeout:
@@ -240,38 +224,23 @@ def get_client(
input={"messages": [{"role": "user", "content": "Foo"}]},
)
```
???+ example "Skip auto-loading API key from environment:"
```python
from langgraph_sdk import get_client
# Don't load API key from environment variables
client = get_client(
url="http://localhost:8123",
api_key=None
)
```
"""
transport: httpx.AsyncBaseTransport | None = None
if url is None:
url = "http://api"
if os.environ.get("__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT") == "true":
transport = get_asgi_transport()(app=None, root_path="/noauth")
_registered_transports.append(transport)
url = "http://api"
else:
try:
from langgraph_api.server import app # type: ignore
url = "http://api"
transport = get_asgi_transport()(app, root_path="/noauth")
except Exception:
logger.debug(
"Failed to connect to in-process LangGraph server. Deferring configuration.",
exc_info=True,
)
transport = get_asgi_transport()(app=None, root_path="/noauth")
_registered_transports.append(transport)
url = "http://localhost:8123"
if transport is None:
transport = httpx.AsyncHTTPTransport(retries=5)
@@ -1059,7 +1028,6 @@ class AssistantsClient:
f"/assistants/{assistant_id}", headers=headers, params=params
)
@overload
async def search(
self,
*,
@@ -1071,43 +1039,9 @@ class AssistantsClient:
sort_by: AssistantSortBy | None = None,
sort_order: SortOrder | None = None,
select: list[AssistantSelectField] | None = None,
response_format: Literal["object"],
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> AssistantsSearchResponse: ...
@overload
async def search(
self,
*,
metadata: Json = None,
graph_id: str | None = None,
name: str | None = None,
limit: int = 10,
offset: int = 0,
sort_by: AssistantSortBy | None = None,
sort_order: SortOrder | None = None,
select: list[AssistantSelectField] | None = None,
response_format: Literal["array"] = "array",
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Assistant]: ...
async def search(
self,
*,
metadata: Json = None,
graph_id: str | None = None,
name: str | None = None,
limit: int = 10,
offset: int = 0,
sort_by: AssistantSortBy | None = None,
sort_order: SortOrder | None = None,
select: list[AssistantSelectField] | None = None,
response_format: Literal["array", "object"] = "array",
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> AssistantsSearchResponse | list[Assistant]:
) -> list[Assistant]:
"""Search for assistants.
Args:
@@ -1120,38 +1054,24 @@ class AssistantsClient:
offset: The number of results to skip.
sort_by: The field to sort by.
sort_order: The order to sort by.
select: Specific assistant fields to include in the response.
response_format: Controls the response shape. Use ``"array"`` (default)
to return a bare list of assistants, or ``"object"`` to return
a mapping containing assistants plus pagination metadata.
Defaults to "array", though this default will be changed to "object" in a future release.
headers: Optional custom headers to include with the request.
params: Optional query parameters to include with the request.
Returns:
A list of assistants (when ``response_format=\"array\"``) or a mapping
with the assistants and the next pagination cursor (when
``response_format=\"object\"``).
A list of assistants.
???+ example "Example Usage"
```python
client = get_client(url="http://localhost:2024")
response = await client.assistants.search(
assistants = await client.assistants.search(
metadata = {"name":"my_name"},
graph_id="my_graph_id",
limit=5,
offset=5,
response_format="object"
offset=5
)
next_cursor = response["next"]
assistants = response["assistants"]
```
"""
if response_format not in ("array", "object"):
raise ValueError(
f"response_format must be 'array' or 'object', got {response_format!r}"
)
payload: dict[str, Any] = {
"limit": limit,
"offset": offset,
@@ -1168,25 +1088,12 @@ class AssistantsClient:
payload["sort_order"] = sort_order
if select:
payload["select"] = select
next_cursor: str | None = None
def capture_pagination(response: httpx.Response) -> None:
nonlocal next_cursor
next_cursor = response.headers.get("X-Pagination-Next")
assistants = cast(
list[Assistant],
await self.http.post(
"/assistants/search",
json=payload,
headers=headers,
params=params,
on_response=capture_pagination if response_format == "object" else None,
),
return await self.http.post(
"/assistants/search",
json=payload,
headers=headers,
params=params,
)
if response_format == "object":
return {"assistants": assistants, "next": next_cursor}
return assistants
async def count(
self,
@@ -1974,7 +1881,7 @@ class RunsClient:
thread_id: str,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -2004,7 +1911,7 @@ class RunsClient:
thread_id: None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -2030,7 +1937,7 @@ class RunsClient:
thread_id: str | None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -2189,7 +2096,7 @@ class RunsClient:
thread_id: None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -2215,7 +2122,7 @@ class RunsClient:
thread_id: str,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -2242,7 +2149,7 @@ class RunsClient:
thread_id: str | None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -2444,7 +2351,7 @@ class RunsClient:
thread_id: str,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
@@ -2471,7 +2378,7 @@ class RunsClient:
thread_id: None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
@@ -2495,7 +2402,7 @@ class RunsClient:
thread_id: str | None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
@@ -2968,7 +2875,7 @@ class CronClient:
assistant_id: str,
*,
schedule: str,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
context: Context | None = None,
@@ -3051,7 +2958,7 @@ class CronClient:
assistant_id: str,
*,
schedule: str,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
context: Context | None = None,
@@ -3563,7 +3470,7 @@ class StoreClient:
def get_sync_client(
*,
url: str | None = None,
api_key: str | None = NOT_PROVIDED,
api_key: str | None = None,
headers: Mapping[str, str] | None = None,
timeout: TimeoutTypes | None = None,
) -> SyncLangGraphClient:
@@ -3571,13 +3478,12 @@ def get_sync_client(
Args:
url: The URL of the LangGraph API.
api_key: API key for authentication. Can be:
- A string: use this exact API key
- `None`: explicitly skip loading from environment variables
- Not provided (default): auto-load from environment in this order:
1. `LANGGRAPH_API_KEY`
2. `LANGSMITH_API_KEY`
3. `LANGCHAIN_API_KEY`
api_key: The API key. If not provided, it will be read from the environment.
Precedence:
1. explicit argument
2. LANGGRAPH_API_KEY
3. LANGSMITH_API_KEY
4. LANGCHAIN_API_KEY
headers: Optional custom headers
timeout: Optional timeout configuration for the HTTP client.
Accepts an httpx.Timeout instance, a float (seconds), or a tuple of timeouts.
@@ -3598,18 +3504,6 @@ def get_sync_client(
# example usage: client.<model>.<method_name>()
assistant = client.assistants.get(assistant_id="some_uuid")
```
???+ example "Skip auto-loading API key from environment:"
```python
from langgraph_sdk import get_sync_client
# Don't load API key from environment variables
client = get_sync_client(
url="http://localhost:8123",
api_key=None
)
```
"""
if url is None:
@@ -4395,7 +4289,6 @@ class SyncAssistantsClient:
"""
self.http.delete(f"/assistants/{assistant_id}", headers=headers, params=params)
@overload
def search(
self,
*,
@@ -4407,43 +4300,9 @@ class SyncAssistantsClient:
sort_by: AssistantSortBy | None = None,
sort_order: SortOrder | None = None,
select: list[AssistantSelectField] | None = None,
response_format: Literal["object"],
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> AssistantsSearchResponse: ...
@overload
def search(
self,
*,
metadata: Json = None,
graph_id: str | None = None,
name: str | None = None,
limit: int = 10,
offset: int = 0,
sort_by: AssistantSortBy | None = None,
sort_order: SortOrder | None = None,
select: list[AssistantSelectField] | None = None,
response_format: Literal["array"] = "array",
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Assistant]: ...
def search(
self,
*,
metadata: Json = None,
graph_id: str | None = None,
name: str | None = None,
limit: int = 10,
offset: int = 0,
sort_by: AssistantSortBy | None = None,
sort_order: SortOrder | None = None,
select: list[AssistantSelectField] | None = None,
response_format: Literal["array", "object"] = "array",
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> AssistantsSearchResponse | list[Assistant]:
) -> list[Assistant]:
"""Search for assistants.
Args:
@@ -4454,37 +4313,23 @@ class SyncAssistantsClient:
The filtering logic will match assistants where 'name' is a substring (case insensitive) of the assistant name.
limit: The maximum number of results to return.
offset: The number of results to skip.
sort_by: The field to sort by.
sort_order: The order to sort by.
select: Specific assistant fields to include in the response.
response_format: Controls the response shape. Use ``"array"`` (default)
to return a bare list of assistants, or ``"object"`` to return
a mapping containing assistants plus pagination metadata.
Defaults to "array", though this default will be changed to "object" in a future release.
headers: Optional custom headers to include with the request.
Returns:
A list of assistants (when ``response_format=\"array\"``) or a mapping
with the assistants and the next pagination cursor (when
``response_format=\"object\"``).
A list of assistants.
???+ example "Example Usage"
```python
client = get_sync_client(url="http://localhost:2024")
response = client.assistants.search(
assistants = client.assistants.search(
metadata = {"name":"my_name"},
graph_id="my_graph_id",
limit=5,
offset=5,
response_format="object",
offset=5
)
assistants = response["assistants"]
next_cursor = response["next"]
```
"""
if response_format not in ("array", "object"):
raise ValueError("response_format must be 'array' or 'object'")
payload: dict[str, Any] = {
"limit": limit,
"offset": offset,
@@ -4501,25 +4346,12 @@ class SyncAssistantsClient:
payload["sort_order"] = sort_order
if select:
payload["select"] = select
next_cursor: str | None = None
def capture_pagination(response: httpx.Response) -> None:
nonlocal next_cursor
next_cursor = response.headers.get("X-Pagination-Next")
assistants = cast(
list[Assistant],
self.http.post(
"/assistants/search",
json=payload,
headers=headers,
params=params,
on_response=capture_pagination if response_format == "object" else None,
),
return self.http.post(
"/assistants/search",
json=payload,
headers=headers,
params=params,
)
if response_format == "object":
return {"assistants": assistants, "next": next_cursor}
return assistants
def count(
self,
@@ -5289,7 +5121,7 @@ class SyncRunsClient:
thread_id: str,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -5318,7 +5150,7 @@ class SyncRunsClient:
thread_id: None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -5345,7 +5177,7 @@ class SyncRunsClient:
thread_id: str | None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -5500,7 +5332,7 @@ class SyncRunsClient:
thread_id: None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -5526,7 +5358,7 @@ class SyncRunsClient:
thread_id: str,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -5553,7 +5385,7 @@ class SyncRunsClient:
thread_id: str | None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
stream_mode: StreamMode | Sequence[StreamMode] = "values",
stream_subgraphs: bool = False,
@@ -5755,7 +5587,7 @@ class SyncRunsClient:
thread_id: str,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
@@ -5782,7 +5614,7 @@ class SyncRunsClient:
thread_id: None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
@@ -5806,7 +5638,7 @@ class SyncRunsClient:
thread_id: str | None,
assistant_id: str,
*,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
command: Command | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
@@ -6259,7 +6091,7 @@ class SyncCronClient:
assistant_id: str,
*,
schedule: str,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
context: Context | None = None,
@@ -6338,7 +6170,7 @@ class SyncCronClient:
assistant_id: str,
*,
schedule: str,
input: Input | None = None,
input: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
config: Config | None = None,
context: Context | None = None,
@@ -1,554 +0,0 @@
"""Custom encryption support for LangGraph.
.. warning::
This API is in beta and may change in future versions.
This module provides a framework for implementing custom at-rest encryption
in LangGraph applications. Similar to the Auth system, it allows developers
to define custom encryption and decryption handlers that are executed
server-side.
"""
from __future__ import annotations
import functools
import inspect
import typing
import warnings
from langgraph_sdk.encryption import types
class LangGraphBetaWarning(UserWarning):
"""Warning for beta features in LangGraph SDK."""
@functools.lru_cache(maxsize=1)
def _warn_encryption_beta() -> None:
warnings.warn(
"The Encryption API is in beta and may change in future versions.",
LangGraphBetaWarning,
stacklevel=4,
)
class DuplicateHandlerError(Exception):
"""Raised when attempting to register a duplicate encryption/decryption handler."""
pass
def _validate_handler(fn: typing.Callable, handler_type: str) -> None:
"""Validate that a handler function has the correct signature.
Args:
fn: The handler function to validate
handler_type: Description of the handler for error messages
Raises:
TypeError: If the handler is not an async function or has wrong parameter count
"""
if not inspect.iscoroutinefunction(fn):
raise TypeError(f"{handler_type} must be an async function, got {type(fn)}")
sig = inspect.signature(fn)
params = [
p
for p in sig.parameters.values()
if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
]
if len(params) != 2:
raise TypeError(
f"{handler_type} must accept exactly 2 parameters "
f"(ctx, data), got {len(params)}"
)
class _JsonEncryptDecorators:
"""Dynamic decorator factory for JSON encryption handlers.
Supports both default and model-specific handlers:
- @encrypt.json - default handler for all models
- @encrypt.json.thread - handler for thread model
"""
def __init__(self, parent: Encryption):
self._parent = parent
def __call__(self, fn: types.JsonEncryptor) -> types.JsonEncryptor:
"""Register the default JSON encryption handler.
Args:
fn: The handler function
Returns:
The registered handler function
Raises:
DuplicateHandlerError: If handler already registered
TypeError: If handler has invalid signature
"""
if self._parent._json_encryptor is not None:
raise DuplicateHandlerError("Default JSON encryptor already registered")
_validate_handler(fn, "Default JSON encryptor")
self._parent._json_encryptor = fn
return fn
def __getattr__(
self, model: str
) -> typing.Callable[[types.JsonEncryptor], types.JsonEncryptor]:
"""Dynamic attribute access for model-specific handlers.
Allows @encryption.encrypt.json.thread, @encryption.encrypt.json.assistant, etc.
Raises:
DuplicateHandlerError: If handler already registered for this model
TypeError: If handler has invalid signature
"""
def decorator(fn: types.JsonEncryptor) -> types.JsonEncryptor:
if model in self._parent._json_encryptors:
raise DuplicateHandlerError(
f"JSON encryptor for model '{model}' already registered"
)
_validate_handler(fn, f"JSON encryptor for model '{model}'")
self._parent._json_encryptors[model] = fn
return fn
return decorator
class _JsonDecryptDecorators:
"""Dynamic decorator factory for JSON decryption handlers.
Supports both default and model-specific handlers:
- @encryption.decrypt.json - default handler for all models
- @encryption.decrypt.json.thread - handler for thread model
"""
def __init__(self, parent: Encryption):
self._parent = parent
def __call__(self, fn: types.JsonDecryptor) -> types.JsonDecryptor:
"""Register the default JSON decryption handler.
Args:
fn: The handler function
Returns:
The registered handler function
Raises:
DuplicateHandlerError: If handler already registered
TypeError: If handler has invalid signature
"""
if self._parent._json_decryptor is not None:
raise DuplicateHandlerError("Default JSON decryptor already registered")
_validate_handler(fn, "Default JSON decryptor")
self._parent._json_decryptor = fn
return fn
def __getattr__(
self, model: str
) -> typing.Callable[[types.JsonDecryptor], types.JsonDecryptor]:
"""Dynamic attribute access for model-specific handlers.
Allows @encryption.decrypt.json.thread, @encryption.decrypt.json.assistant, etc.
Raises:
DuplicateHandlerError: If handler already registered for this model
TypeError: If handler has invalid signature
"""
def decorator(fn: types.JsonDecryptor) -> types.JsonDecryptor:
if model in self._parent._json_decryptors:
raise DuplicateHandlerError(
f"JSON decryptor for model '{model}' already registered"
)
_validate_handler(fn, f"JSON decryptor for model '{model}'")
self._parent._json_decryptors[model] = fn
return fn
return decorator
class _EncryptDecorators:
"""Decorators for encryption handlers.
Provides @encryption.encrypt.blob and @encryption.encrypt.json decorators for
registering encryption functions.
"""
def __init__(self, parent: Encryption):
self._parent = parent
self._json = _JsonEncryptDecorators(parent)
def blob(self, fn: types.BlobEncryptor) -> types.BlobEncryptor:
"""Register a blob encryption handler.
The handler will be called to encrypt opaque data like checkpoint blobs.
Example:
```python
@encryption.encrypt.blob
async def encrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
# Encrypt the blob using your encryption service
return encrypted_blob
```
Args:
fn: The encryption handler function
Returns:
The registered handler function
Raises:
DuplicateHandlerError: If blob encryptor already registered
TypeError: If handler has invalid signature
"""
if self._parent._blob_encryptor is not None:
raise DuplicateHandlerError("Blob encryptor already registered")
_validate_handler(fn, "Blob encryptor")
self._parent._blob_encryptor = fn
return fn
@property
def json(self) -> _JsonEncryptDecorators:
"""Access JSON encryption decorators.
Supports model-specific handlers:
- @encryption.encrypt.json - default handler for all models
- @encryption.encrypt.json.thread - handler for thread model only
- @encryption.encrypt.json.assistant - handler for assistant model only
Example:
```python
@encryption.encrypt.json
async def default_encrypt(ctx: EncryptionContext, data: dict) -> dict:
# Default encryption for all models
return encrypt_data(data)
@encryption.encrypt.json.thread
async def encrypt_thread(ctx: EncryptionContext, data: dict) -> dict:
# Special encryption for thread model only
return encrypt_thread_data(data)
```
"""
return self._json
class _DecryptDecorators:
"""Decorators for decryption handlers.
Provides @encryption.decrypt.blob and @encryption.decrypt.json decorators for
registering decryption functions.
"""
def __init__(self, parent: Encryption):
self._parent = parent
self._json = _JsonDecryptDecorators(parent)
def blob(self, fn: types.BlobDecryptor) -> types.BlobDecryptor:
"""Register a blob decryption handler.
The handler will be called to decrypt opaque data like checkpoint blobs.
Example:
```python
@encryption.decrypt.blob
async def decrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
# Decrypt the blob using your encryption service
return decrypted_blob
```
Args:
fn: The decryption handler function
Returns:
The registered handler function
Raises:
DuplicateHandlerError: If blob decryptor already registered
TypeError: If handler has invalid signature
"""
if self._parent._blob_decryptor is not None:
raise DuplicateHandlerError("Blob decryptor already registered")
_validate_handler(fn, "Blob decryptor")
self._parent._blob_decryptor = fn
return fn
@property
def json(self) -> _JsonDecryptDecorators:
"""Access JSON decryption decorators.
Supports model-specific handlers:
- @encryption.decrypt.json - default handler for all models
- @encryption.decrypt.json.thread - handler for thread model only
- @encryption.decrypt.json.assistant - handler for assistant model only
Example:
```python
@encryption.decrypt.json
async def default_decrypt(ctx: EncryptionContext, data: dict) -> dict:
# Default decryption for all models
return decrypt_data(data)
@encryption.decrypt.json.thread
async def decrypt_thread(ctx: EncryptionContext, data: dict) -> dict:
# Special decryption for thread model only
return decrypt_thread_data(data)
```
"""
return self._json
class Encryption:
"""Add custom at-rest encryption to your LangGraph application.
.. warning::
This API is in beta and may change in future versions.
The Encryption class provides a system for implementing custom encryption
of data at rest in LangGraph applications. It supports encryption of
both opaque blobs (like checkpoints) and structured JSON data (like
metadata, context, kwargs, values, etc.).
To use, create a separate Python file and add the path to the file to your
LangGraph API configuration file (`langgraph.json`). Within that file, create
an instance of the Encryption class and register encryption and decryption
handlers as needed.
Example `langgraph.json` file:
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./my_agent/agent.py:graph"
},
"env": ".env",
"encryption": {
"path": "./encryption.py:my_encryption"
}
}
```
Then the LangGraph server will load your encryption file and use it to
encrypt/decrypt data at rest.
???+ example "Basic Usage"
```python
from langgraph_sdk import Encryption, EncryptionContext
my_encryption = Encryption()
@my_encryption.encrypt.blob
async def encrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
# Call your encryption service
return encrypted_blob
@my_encryption.decrypt.blob
async def decrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
# Call your decryption service
return decrypted_blob
@my_encryption.encrypt.json
async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
# Practical encryption strategy:
# - "owner" field: unencrypted (for search/filtering)
# - "my.customer.org/" prefixed fields: encrypt VALUES only
# - All other fields: pass through unencrypted
encrypted = {}
for key, value in data.items():
if key.startswith("my.customer.org/"):
# Encrypt VALUE for sensitive customer data
encrypted[key] = encrypt_value(value)
else:
# Pass through (including "owner" for search)
encrypted[key] = value
return encrypted
@my_encryption.decrypt.json
async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
# Decrypt VALUES for "my.customer.org/" prefixed fields
decrypted = {}
for key, value in data.items():
if key.startswith("my.customer.org/"):
decrypted[key] = decrypt_value(value)
else:
decrypted[key] = value
return decrypted
```
???+ example "Model-Specific Handlers"
You can register different encryption handlers for different model types
(thread, assistant, run, cron, checkpoint, etc.):
```python
from langgraph_sdk import Encryption, EncryptionContext
my_encryption = Encryption()
# Default handler for models without specific handlers
@my_encryption.encrypt.json
async def default_encrypt(ctx: EncryptionContext, data: dict) -> dict:
return standard_encrypt(data)
# Thread-specific handler (uses different KMS key)
@my_encryption.encrypt.json.thread
async def encrypt_thread(ctx: EncryptionContext, data: dict) -> dict:
return encrypt_with_thread_key(data)
# Assistant-specific handler
@my_encryption.encrypt.json.assistant
async def encrypt_assistant(ctx: EncryptionContext, data: dict) -> dict:
return encrypt_with_assistant_key(data)
# Same pattern for decryption
@my_encryption.decrypt.json
async def default_decrypt(ctx: EncryptionContext, data: dict) -> dict:
return standard_decrypt(data)
@my_encryption.decrypt.json.thread
async def decrypt_thread(ctx: EncryptionContext, data: dict) -> dict:
return decrypt_with_thread_key(data)
```
???+ example "Field-Specific Logic"
The `ctx.field` attribute tells you which specific field is being encrypted,
allowing different logic within the same model:
```python
@my_encryption.encrypt.json.thread
async def encrypt_thread(ctx: EncryptionContext, data: dict) -> dict:
if ctx.field == "metadata":
# Thread metadata - standard encryption
return encrypt_standard(data)
elif ctx.field == "values":
# Thread values - more sensitive, use stronger encryption
return encrypt_sensitive(data)
else:
return encrypt_standard(data)
```
"""
__slots__ = (
"_blob_decryptor",
"_blob_encryptor",
"_context_handler",
"_json_decryptor",
"_json_decryptors",
"_json_encryptor",
"_json_encryptors",
"decrypt",
"encrypt",
)
types = types
"""Reference to encryption type definitions.
Provides access to all type definitions used in the encryption system,
including EncryptionContext, BlobEncryptor, BlobDecryptor,
JsonEncryptor, and JsonDecryptor.
"""
def __init__(self) -> None:
"""Initialize the Encryption instance."""
_warn_encryption_beta()
self.encrypt = _EncryptDecorators(self)
self.decrypt = _DecryptDecorators(self)
self._blob_encryptor: types.BlobEncryptor | None = None
self._blob_decryptor: types.BlobDecryptor | None = None
self._json_encryptor: types.JsonEncryptor | None = None
self._json_decryptor: types.JsonDecryptor | None = None
self._json_encryptors: dict[str, types.JsonEncryptor] = {}
self._json_decryptors: dict[str, types.JsonDecryptor] = {}
self._context_handler: types.ContextHandler | None = None
def context(self, fn: types.ContextHandler) -> types.ContextHandler:
"""Register a context handler to derive encryption context from auth.
The handler receives the authenticated user and current EncryptionContext,
and returns a dict that becomes ctx.metadata for encrypt/decrypt handlers.
This allows encryption context to be derived from JWT claims or other
auth-derived data instead of requiring a separate X-Encryption-Context header.
Note: The context handler is called once per request in middleware,
so ctx.model and ctx.field will be None in the handler.
Example:
```python
from langgraph_sdk import Encryption, EncryptionContext
from starlette.authentication import BaseUser
encryption = Encryption()
@encryption.context
async def get_context(user: BaseUser, ctx: EncryptionContext) -> dict:
# Derive encryption context from authenticated user
return {
**ctx.metadata, # preserve X-Encryption-Context header if present
"tenant_id": user.tenant_id,
}
```
Args:
fn: The context handler function
Returns:
The registered handler function
"""
self._context_handler = fn
return fn
def get_json_encryptor(
self, model: str | None = None
) -> types.JsonEncryptor | None:
"""Get the JSON encryptor for a specific model.
Args:
model: The model type (e.g., "thread", "assistant"). If None, returns default.
Returns:
Model-specific encryptor if registered, otherwise default encryptor, or None.
"""
if model and model in self._json_encryptors:
return self._json_encryptors[model]
return self._json_encryptor
def get_json_decryptor(
self, model: str | None = None
) -> types.JsonDecryptor | None:
"""Get the JSON decryptor for a specific model.
Args:
model: The model type (e.g., "thread", "assistant"). If None, returns default.
Returns:
Model-specific decryptor if registered, otherwise default decryptor, or None.
"""
if model and model in self._json_decryptors:
return self._json_decryptors[model]
return self._json_decryptor
def __repr__(self) -> str:
handlers = []
if self._blob_encryptor:
handlers.append("blob_encryptor")
if self._blob_decryptor:
handlers.append("blob_decryptor")
if self._json_encryptor:
handlers.append("json_encryptor")
if self._json_decryptor:
handlers.append("json_decryptor")
if self._json_encryptors:
handlers.append(f"json_encryptors({list(self._json_encryptors.keys())})")
if self._json_decryptors:
handlers.append(f"json_decryptors({list(self._json_decryptors.keys())})")
if self._context_handler:
handlers.append("context_handler")
return f"Encryption(handlers=[{', '.join(handlers)}])"
@@ -1,147 +0,0 @@
"""Encryption and decryption types for LangGraph.
This module defines the core types used for custom at-rest encryption
in LangGraph. It includes context types and typed dictionaries for
encryption operations.
"""
from __future__ import annotations
import typing
from collections.abc import Awaitable, Callable
Json = dict[str, typing.Any]
"""JSON-serializable dictionary type for structured data encryption."""
class EncryptionContext:
"""Context passed to encryption/decryption handlers.
Contains arbitrary non-secret key-values that will be stored on encrypt.
These key-values are intended to be sent to an external service that
manages keys and handles the actual encryption and decryption of data.
Attributes:
model: The model type being encrypted (e.g., "assistant", "thread", "run", "checkpoint")
field: The specific field being encrypted (e.g., "metadata", "context", "kwargs", "values")
metadata: Additional context metadata that can be used for encryption decisions
"""
__slots__ = ("field", "metadata", "model")
def __init__(
self,
model: str | None = None,
metadata: dict[str, typing.Any] | None = None,
field: str | None = None,
):
self.model = model
self.field = field
self.metadata = metadata or {}
def __repr__(self) -> str:
return f"EncryptionContext(model={self.model!r}, field={self.field!r}, metadata={self.metadata!r})"
BlobEncryptor = Callable[[EncryptionContext, bytes], Awaitable[bytes]]
"""Handler for encrypting opaque blob data like checkpoints.
Note: Must be an async function. Encryption typically involves I/O operations
(calling external KMS services), which should be async.
Args:
ctx: Encryption context with model type and metadata
blob: The raw bytes to encrypt
Returns:
Awaitable that resolves to encrypted bytes
"""
BlobDecryptor = Callable[[EncryptionContext, bytes], Awaitable[bytes]]
"""Handler for decrypting opaque blob data like checkpoints.
Note: Must be an async function. Decryption typically involves I/O operations
(calling external KMS services), which should be async.
Args:
ctx: Encryption context with model type and metadata
blob: The encrypted bytes to decrypt
Returns:
Awaitable that resolves to decrypted bytes
"""
JsonEncryptor = Callable[[EncryptionContext, Json], Awaitable[Json]]
"""Handler for encrypting structured JSON data.
Note: Must be an async function. Encryption typically involves I/O operations
(calling external KMS services), which should be async.
Used for encrypting structured data like metadata, context, kwargs, values,
and other JSON-serializable fields across different model types.
Maps plaintext fields to encrypted fields. A practical approach:
- Keep "owner" field unencrypted for search/filtering
- Encrypt VALUES (not keys) for fields with specific prefix (e.g., "my.customer.org/")
- Pass through all other fields unencrypted
Example:
Input: {"owner": "user123", "my.customer.org/email": "john@example.com", "tenant_id": "t-456"}
Output: {"owner": "user123", "my.customer.org/email": "ENCRYPTED", "tenant_id": "t-456"}
Note: Encrypted field VALUES cannot be reliably searched, as most real-world
encryption implementations use nonces (non-deterministic encryption).
Only unencrypted fields can be used in search queries.
Args:
ctx: Encryption context with model type, field name, and metadata
data: The plaintext JSON dictionary
Returns:
Awaitable that resolves to encrypted JSON dictionary
"""
JsonDecryptor = Callable[[EncryptionContext, Json], Awaitable[Json]]
"""Handler for decrypting structured JSON data.
Note: Must be an async function. Decryption typically involves I/O operations
(calling external KMS services), which should be async.
Inverse of JsonEncryptor. Must be able to decrypt data that
was encrypted by the corresponding encryptor.
Args:
ctx: Encryption context with model type, field name, and metadata
data: The encrypted JSON dictionary
Returns:
Awaitable that resolves to decrypted JSON dictionary
"""
if typing.TYPE_CHECKING:
from starlette.authentication import BaseUser
ContextHandler = Callable[
["BaseUser", EncryptionContext], Awaitable[dict[str, typing.Any]]
]
"""Handler for deriving encryption context from authenticated user info.
Note: Must be an async function as it may involve I/O operations.
The context handler is called once per request in middleware (after auth),
allowing encryption context to be derived from JWT claims, user properties,
or other auth-derived data instead of requiring a separate X-Encryption-Context header.
The return value becomes ctx.metadata for subsequent encrypt/decrypt operations
and is persisted with encrypted data for later decryption.
Note: ctx.model and ctx.field will be None in context handlers since
the handler runs once per request before any specific model/field is known.
Args:
user: The authenticated user (from Starlette's AuthenticationMiddleware)
ctx: Current encryption context with metadata from X-Encryption-Context header
Returns:
Awaitable that resolves to dict that becomes the new ctx.metadata
"""
+3 -66
View File
@@ -3,20 +3,15 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import Field
from datetime import datetime
from typing import (
Any,
ClassVar,
Literal,
NamedTuple,
Protocol,
TypeAlias,
Union,
TypedDict,
)
from typing_extensions import TypedDict
Json = dict[str, Any] | None
"""Represents a JSON-like structure, which can be None or a dictionary with string keys and any values."""
@@ -148,6 +143,8 @@ SortOrder = Literal["asc", "desc"]
The order to sort by.
"""
Context: TypeAlias = dict[str, Any]
class Config(TypedDict, total=False):
"""Configuration options for a call."""
@@ -246,15 +243,6 @@ class Assistant(AssistantBase):
"""The last time the assistant was updated."""
class AssistantsSearchResponse(TypedDict):
"""Paginated response for assistant search results."""
assistants: list[Assistant]
"""The assistants returned for the current search page."""
next: str | None
"""Pagination cursor from the ``X-Pagination-Next`` response header."""
class Interrupt(TypedDict):
"""Represents an interruption in the execution flow."""
@@ -571,54 +559,3 @@ class RunCreateMetadata(TypedDict):
thread_id: str | None
"""The ID of the thread."""
class _TypedDictLikeV1(Protocol):
"""Protocol to represent types that behave like TypedDicts
Version 1: using `ClassVar` for keys."""
__required_keys__: ClassVar[frozenset[str]]
__optional_keys__: ClassVar[frozenset[str]]
class _TypedDictLikeV2(Protocol):
"""Protocol to represent types that behave like TypedDicts
Version 2: not using `ClassVar` for keys."""
__required_keys__: frozenset[str]
__optional_keys__: frozenset[str]
class _DataclassLike(Protocol):
"""Protocol to represent types that behave like dataclasses.
Inspired by the private _DataclassT from dataclasses that uses a similar protocol as a bound.
"""
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
class _BaseModelLike(Protocol):
"""Protocol to represent types that behave like Pydantic `BaseModel`."""
model_config: ClassVar[dict[str, Any]]
__pydantic_core_schema__: ClassVar[Any]
def model_dump(
self,
**kwargs: Any,
) -> dict[str, Any]: ...
_JSONLike: TypeAlias = None | str | int | float | bool
_JSONMap: TypeAlias = Mapping[
str, Union[_JSONLike, list[_JSONLike], "_JSONMap", list["_JSONMap"]]
]
Input: TypeAlias = (
_TypedDictLikeV1 | _TypedDictLikeV2 | _DataclassLike | _BaseModelLike | _JSONMap
)
Context: TypeAlias = Input
+2 -13
View File
@@ -23,19 +23,8 @@ Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
[dependency-groups]
test = [
"pytest",
"pytest-asyncio",
"pytest-mock",
"pytest-watch",
]
lint = [
"ruff==0.14.7",
"codespell",
"mypy==1.19.0",
"ty==0.0.1a27",
"starlette",
]
test = ["pytest", "pytest-asyncio", "pytest-mock", "pytest-watch"]
lint = ["ruff", "codespell", "mypy"]
dev = [
{ include-group = "test" },
{ include-group = "lint" },
@@ -1,91 +0,0 @@
from __future__ import annotations
import httpx
import pytest
from langgraph_sdk.client import (
AssistantsClient,
HttpClient,
SyncAssistantsClient,
SyncHttpClient,
)
def _assistant_payload() -> dict[str, object]:
return {
"assistant_id": "asst_123",
"graph_id": "graph_123",
"config": {"configurable": {"foo": "bar"}},
"context": {"foo": "bar"},
"created_at": "2024-01-01T00:00:00Z",
"metadata": {"env": "test"},
"version": 1,
"name": "My Assistant",
"description": "Example",
"updated_at": "2024-01-02T00:00:00Z",
}
@pytest.mark.asyncio
async def test_assistants_search_returns_list_by_default():
assistant = _assistant_payload()
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/assistants/search"
return httpx.Response(200, json=[assistant])
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
assistants_client = AssistantsClient(http_client)
result = await assistants_client.search(limit=3)
assert result == [assistant]
@pytest.mark.asyncio
async def test_assistants_search_can_return_object_with_pagination_metadata():
assistant = _assistant_payload()
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/assistants/search"
return httpx.Response(
200,
headers={"X-Pagination-Next": "42"},
json=[assistant],
)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
assistants_client = AssistantsClient(http_client)
result = await assistants_client.search(response_format="object")
assert result == {"assistants": [assistant], "next": "42"}
def test_sync_assistants_search_can_return_object_with_pagination_metadata():
assistant = _assistant_payload()
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/assistants/search"
return httpx.Response(
200,
headers={"X-Pagination-Next": "84"},
json=[assistant],
)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
assistants_client = SyncAssistantsClient(http_client)
result = assistants_client.search(response_format="object")
assert result == {"assistants": [assistant], "next": "84"}
-92
View File
@@ -1,92 +0,0 @@
import pytest
from langgraph_sdk.encryption import DuplicateHandlerError, Encryption
class TestHandlerValidation:
"""Test duplicate handler and signature validation."""
def test_duplicate_handlers_raise_error(self):
"""Registering the same handler type twice raises DuplicateHandlerError."""
encryption = Encryption()
@encryption.encrypt.blob
async def blob_enc(_ctx, data):
return data
@encryption.decrypt.blob
async def blob_dec(_ctx, data):
return data
@encryption.encrypt.json
async def json_enc(_ctx, data):
return data
@encryption.decrypt.json
async def json_dec(_ctx, data):
return data
@encryption.encrypt.json.thread
async def thread_enc(_ctx, data):
return data
@encryption.decrypt.json.custom
async def custom_dec(_ctx, data):
return data
# All duplicates should raise
with pytest.raises(DuplicateHandlerError):
@encryption.encrypt.blob
async def dup(_ctx, data):
return data
with pytest.raises(DuplicateHandlerError):
@encryption.decrypt.blob
async def dup(_ctx, data):
return data
with pytest.raises(DuplicateHandlerError):
@encryption.encrypt.json
async def dup(_ctx, data):
return data
with pytest.raises(DuplicateHandlerError):
@encryption.decrypt.json
async def dup(_ctx, data):
return data
with pytest.raises(DuplicateHandlerError):
@encryption.encrypt.json.thread
async def dup(_ctx, data):
return data
with pytest.raises(DuplicateHandlerError):
@encryption.decrypt.json.custom
async def dup(_ctx, data):
return data
def test_handlers_must_be_async(self):
"""Sync functions raise TypeError."""
encryption = Encryption()
with pytest.raises(TypeError, match="must be an async function"):
@encryption.encrypt.blob
def sync_handler(_ctx, data):
return data
def test_handlers_must_have_two_params(self):
"""Wrong parameter count raises TypeError."""
encryption = Encryption()
with pytest.raises(TypeError, match="must accept exactly 2 parameters"):
@encryption.encrypt.blob # type: ignore[arg-type]
async def wrong_params(ctx):
return ctx
-22
View File
@@ -30,28 +30,6 @@ async def test_serde_pydantic():
assert result["name"] == "test"
assert result["age"] == 25
nested_result = await _serde_roundtrip({"data": model})
assert nested_result["data"]["name"] == "test"
assert nested_result["data"]["age"] == 25
async def test_serde_dataclass():
from dataclasses import dataclass
@dataclass
class TestDataClass:
name: str
age: int
data = TestDataClass(name="test", age=25)
result = await _serde_roundtrip(data)
assert result["name"] == "test"
assert result["age"] == 25
nested_result = await _serde_roundtrip({"data": data})
assert nested_result["data"]["name"] == "test"
assert nested_result["data"]["age"] == 25
async def test_serde_pydantic_cls_fails():
# Test that serialization fails gracefully for Pydantic model when not available
-30
View File
@@ -1,30 +0,0 @@
from dataclasses import dataclass
from typing import runtime_checkable
from pydantic import BaseModel
from langgraph_sdk.schema import (
_BaseModelLike,
_DataclassLike,
)
def rc(cls: type) -> type:
return runtime_checkable(cls)
class MyModel(BaseModel):
foo: str
def test_base_model_like():
assert isinstance(MyModel(foo="test"), rc(_BaseModelLike))
@dataclass
class MyDataclass:
foo: str
def test_dataclass_like():
assert isinstance(MyDataclass(foo="test"), rc(_DataclassLike))
@@ -1,70 +0,0 @@
"""Tests for api_key parameter behavior."""
import pytest
from langgraph_sdk import get_client, get_sync_client
class TestSkipAutoLoadApiKey:
"""Test the api_key parameter's auto-loading behavior."""
@pytest.mark.asyncio
async def test_get_client_loads_from_env_by_default(self, monkeypatch):
"""Test that API key is loaded from environment by default."""
monkeypatch.setenv("LANGGRAPH_API_KEY", "test-key-from-env")
client = get_client(url="http://localhost:8123")
assert "x-api-key" in client.http.client.headers
assert client.http.client.headers["x-api-key"] == "test-key-from-env"
await client.aclose()
@pytest.mark.asyncio
async def test_get_client_skips_env_when_sentinel_used(self, monkeypatch):
"""Test that API key is not loaded from environment when None is explicitly passed."""
monkeypatch.setenv("LANGGRAPH_API_KEY", "test-key-from-env")
client = get_client(url="http://localhost:8123", api_key=None)
assert "x-api-key" not in client.http.client.headers
await client.aclose()
@pytest.mark.asyncio
async def test_get_client_uses_explicit_key_when_provided(self, monkeypatch):
"""Test that explicit API key takes precedence over environment."""
monkeypatch.setenv("LANGGRAPH_API_KEY", "test-key-from-env")
client = get_client(
url="http://localhost:8123",
api_key="explicit-key",
)
assert "x-api-key" in client.http.client.headers
assert client.http.client.headers["x-api-key"] == "explicit-key"
await client.aclose()
def test_get_sync_client_loads_from_env_by_default(self, monkeypatch):
"""Test that sync client loads API key from environment by default."""
monkeypatch.setenv("LANGGRAPH_API_KEY", "test-key-from-env")
client = get_sync_client(url="http://localhost:8123")
assert "x-api-key" in client.http.client.headers
assert client.http.client.headers["x-api-key"] == "test-key-from-env"
client.close()
def test_get_sync_client_skips_env_when_sentinel_used(self, monkeypatch):
"""Test that sync client doesn't load from environment when None is explicitly passed."""
monkeypatch.setenv("LANGGRAPH_API_KEY", "test-key-from-env")
client = get_sync_client(url="http://localhost:8123", api_key=None)
assert "x-api-key" not in client.http.client.headers
client.close()
def test_get_sync_client_uses_explicit_key_when_provided(self, monkeypatch):
"""Test that sync client uses explicit API key when provided."""
monkeypatch.setenv("LANGGRAPH_API_KEY", "test-key-from-env")
client = get_sync_client(
url="http://localhost:8123",
api_key="explicit-key",
)
assert "x-api-key" in client.http.client.headers
assert client.http.client.headers["x-api-key"] == "explicit-key"
client.close()
+67 -186
View File
@@ -13,16 +13,17 @@ wheels = [
[[package]]
name = "anyio"
version = "4.12.0"
version = "4.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "idna" },
{ name = "sniffio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" },
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
]
[[package]]
@@ -69,14 +70,14 @@ sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf57
[[package]]
name = "exceptiongroup"
version = "1.3.1"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
[[package]]
@@ -152,15 +153,11 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watch" },
{ name = "ruff" },
{ name = "starlette" },
{ name = "ty" },
]
lint = [
{ name = "codespell" },
{ name = "mypy" },
{ name = "ruff" },
{ name = "starlette" },
{ name = "ty" },
]
test = [
{ name = "pytest" },
@@ -178,22 +175,18 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "codespell" },
{ name = "mypy", specifier = "==1.19.0" },
{ name = "mypy" },
{ name = "pydantic", specifier = ">=2.12.4" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
{ name = "pytest-watch" },
{ name = "ruff", specifier = "==0.14.7" },
{ name = "starlette" },
{ name = "ty", specifier = "==0.0.1a27" },
{ name = "ruff" },
]
lint = [
{ name = "codespell" },
{ name = "mypy", specifier = "==1.19.0" },
{ name = "ruff", specifier = "==0.14.7" },
{ name = "starlette" },
{ name = "ty", specifier = "==0.0.1a27" },
{ name = "mypy" },
{ name = "ruff" },
]
test = [
{ name = "pytest" },
@@ -202,123 +195,49 @@ test = [
{ name = "pytest-watch" },
]
[[package]]
name = "librt"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/37/c3/cdff3c10e2e608490dc0a310ccf11ba777b3943ad4fcead2a2ade98c21e1/librt-0.6.3.tar.gz", hash = "sha256:c724a884e642aa2bbad52bb0203ea40406ad742368a5f90da1b220e970384aae", size = 54209, upload-time = "2025-11-29T14:01:56.058Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/84/859df8db21dedab2538ddfbe1d486dda3eb66a98c6ad7ba754a99e25e45e/librt-0.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45660d26569cc22ed30adf583389d8a0d1b468f8b5e518fcf9bfe2cd298f9dd1", size = 27294, upload-time = "2025-11-29T14:00:35.053Z" },
{ url = "https://files.pythonhosted.org/packages/f7/01/ec3971cf9c4f827f17de6729bdfdbf01a67493147334f4ef8fac68936e3a/librt-0.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:54f3b2177fb892d47f8016f1087d21654b44f7fc4cf6571c1c6b3ea531ab0fcf", size = 27635, upload-time = "2025-11-29T14:00:36.496Z" },
{ url = "https://files.pythonhosted.org/packages/b4/f9/3efe201df84dd26388d2e0afa4c4dc668c8e406a3da7b7319152faf835a1/librt-0.6.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c5b31bed2c2f2fa1fcb4815b75f931121ae210dc89a3d607fb1725f5907f1437", size = 81768, upload-time = "2025-11-29T14:00:37.451Z" },
{ url = "https://files.pythonhosted.org/packages/0a/13/f63e60bc219b17f3d8f3d13423cd4972e597b0321c51cac7bfbdd5e1f7b9/librt-0.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f8ed5053ef9fb08d34f1fd80ff093ccbd1f67f147633a84cf4a7d9b09c0f089", size = 85884, upload-time = "2025-11-29T14:00:38.433Z" },
{ url = "https://files.pythonhosted.org/packages/c2/42/0068f14f39a79d1ce8a19d4988dd07371df1d0a7d3395fbdc8a25b1c9437/librt-0.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f0e4bd9bcb0ee34fa3dbedb05570da50b285f49e52c07a241da967840432513", size = 85830, upload-time = "2025-11-29T14:00:39.418Z" },
{ url = "https://files.pythonhosted.org/packages/14/1c/87f5af3a9e6564f09e50c72f82fc3057fd42d1facc8b510a707d0438c4ad/librt-0.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8f89c8d20dfa648a3f0a56861946eb00e5b00d6b00eea14bc5532b2fcfa8ef1", size = 88086, upload-time = "2025-11-29T14:00:40.555Z" },
{ url = "https://files.pythonhosted.org/packages/05/e5/22153b98b88a913b5b3f266f12e57df50a2a6960b3f8fcb825b1a0cfe40a/librt-0.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecc2c526547eacd20cb9fbba19a5268611dbc70c346499656d6cf30fae328977", size = 86470, upload-time = "2025-11-29T14:00:41.827Z" },
{ url = "https://files.pythonhosted.org/packages/18/3c/ea1edb587799b1edcc22444e0630fa422e32d7aaa5bfb5115b948acc2d1c/librt-0.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fbedeb9b48614d662822ee514567d2d49a8012037fc7b4cd63f282642c2f4b7d", size = 89079, upload-time = "2025-11-29T14:00:42.882Z" },
{ url = "https://files.pythonhosted.org/packages/73/ad/50bb4ae6b07c9f3ab19653e0830a210533b30eb9a18d515efb5a2b9d0c7c/librt-0.6.3-cp310-cp310-win32.whl", hash = "sha256:0765b0fe0927d189ee14b087cd595ae636bef04992e03fe6dfdaa383866c8a46", size = 19820, upload-time = "2025-11-29T14:00:44.211Z" },
{ url = "https://files.pythonhosted.org/packages/7a/12/7426ee78f3b1dbe11a90619d54cb241ca924ca3c0ff9ade3992178e9b440/librt-0.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:8c659f9fb8a2f16dc4131b803fa0144c1dadcb3ab24bb7914d01a6da58ae2457", size = 21332, upload-time = "2025-11-29T14:00:45.427Z" },
{ url = "https://files.pythonhosted.org/packages/8b/80/bc60fd16fe24910bf5974fb914778a2e8540cef55385ab2cb04a0dfe42c4/librt-0.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:61348cc488b18d1b1ff9f3e5fcd5ac43ed22d3e13e862489d2267c2337285c08", size = 27285, upload-time = "2025-11-29T14:00:46.626Z" },
{ url = "https://files.pythonhosted.org/packages/88/3c/26335536ed9ba097c79cffcee148393592e55758fe76d99015af3e47a6d0/librt-0.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64645b757d617ad5f98c08e07620bc488d4bced9ced91c6279cec418f16056fa", size = 27629, upload-time = "2025-11-29T14:00:47.863Z" },
{ url = "https://files.pythonhosted.org/packages/af/fd/2dcedeacfedee5d2eda23e7a49c1c12ce6221b5d58a13555f053203faafc/librt-0.6.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:26b8026393920320bb9a811b691d73c5981385d537ffc5b6e22e53f7b65d4122", size = 82039, upload-time = "2025-11-29T14:00:49.131Z" },
{ url = "https://files.pythonhosted.org/packages/48/ff/6aa11914b83b0dc2d489f7636942a8e3322650d0dba840db9a1b455f3caa/librt-0.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d998b432ed9ffccc49b820e913c8f327a82026349e9c34fa3690116f6b70770f", size = 86560, upload-time = "2025-11-29T14:00:50.403Z" },
{ url = "https://files.pythonhosted.org/packages/76/a1/d25af61958c2c7eb978164aeba0350719f615179ba3f428b682b9a5fdace/librt-0.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e18875e17ef69ba7dfa9623f2f95f3eda6f70b536079ee6d5763ecdfe6cc9040", size = 86494, upload-time = "2025-11-29T14:00:51.383Z" },
{ url = "https://files.pythonhosted.org/packages/7d/4b/40e75d3b258c801908e64b39788f9491635f9554f8717430a491385bd6f2/librt-0.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a218f85081fc3f70cddaed694323a1ad7db5ca028c379c214e3a7c11c0850523", size = 88914, upload-time = "2025-11-29T14:00:52.688Z" },
{ url = "https://files.pythonhosted.org/packages/97/6d/0070c81aba8a169224301c75fb5fb6c3c25ca67e6ced086584fc130d5a67/librt-0.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ef42ff4edd369e84433ce9b188a64df0837f4f69e3d34d3b34d4955c599d03f", size = 86944, upload-time = "2025-11-29T14:00:53.768Z" },
{ url = "https://files.pythonhosted.org/packages/a6/94/809f38887941b7726692e0b5a083dbdc87dbb8cf893e3b286550c5f0b129/librt-0.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e0f2b79993fec23a685b3e8107ba5f8675eeae286675a216da0b09574fa1e47", size = 89852, upload-time = "2025-11-29T14:00:54.71Z" },
{ url = "https://files.pythonhosted.org/packages/58/a3/b0e5b1cda675b91f1111d8ba941da455d8bfaa22f4d2d8963ba96ccb5b12/librt-0.6.3-cp311-cp311-win32.whl", hash = "sha256:fd98cacf4e0fabcd4005c452cb8a31750258a85cab9a59fb3559e8078da408d7", size = 19948, upload-time = "2025-11-29T14:00:55.989Z" },
{ url = "https://files.pythonhosted.org/packages/cc/73/70011c2b37e3be3ece3affd3abc8ebe5cda482b03fd6b3397906321a901e/librt-0.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:e17b5b42c8045867ca9d1f54af00cc2275198d38de18545edaa7833d7e9e4ac8", size = 21406, upload-time = "2025-11-29T14:00:56.874Z" },
{ url = "https://files.pythonhosted.org/packages/91/ee/119aa759290af6ca0729edf513ca390c1afbeae60f3ecae9b9d56f25a8a9/librt-0.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:87597e3d57ec0120a3e1d857a708f80c02c42ea6b00227c728efbc860f067c45", size = 20875, upload-time = "2025-11-29T14:00:57.752Z" },
{ url = "https://files.pythonhosted.org/packages/b4/2c/b59249c566f98fe90e178baf59e83f628d6c38fb8bc78319301fccda0b5e/librt-0.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74418f718083009108dc9a42c21bf2e4802d49638a1249e13677585fcc9ca176", size = 27841, upload-time = "2025-11-29T14:00:58.925Z" },
{ url = "https://files.pythonhosted.org/packages/40/e8/9db01cafcd1a2872b76114c858f81cc29ce7ad606bc102020d6dabf470fb/librt-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:514f3f363d1ebc423357d36222c37e5c8e6674b6eae8d7195ac9a64903722057", size = 27844, upload-time = "2025-11-29T14:01:00.2Z" },
{ url = "https://files.pythonhosted.org/packages/59/4d/da449d3a7d83cc853af539dee42adc37b755d7eea4ad3880bacfd84b651d/librt-0.6.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cf1115207a5049d1f4b7b4b72de0e52f228d6c696803d94843907111cbf80610", size = 84091, upload-time = "2025-11-29T14:01:01.118Z" },
{ url = "https://files.pythonhosted.org/packages/ea/6c/f90306906fb6cc6eaf4725870f0347115de05431e1f96d35114392d31fda/librt-0.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad8ba80cdcea04bea7b78fcd4925bfbf408961e9d8397d2ee5d3ec121e20c08c", size = 88239, upload-time = "2025-11-29T14:01:02.11Z" },
{ url = "https://files.pythonhosted.org/packages/e7/ae/473ce7b423cfac2cb503851a89d9d2195bf615f534d5912bf86feeebbee7/librt-0.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4018904c83eab49c814e2494b4e22501a93cdb6c9f9425533fe693c3117126f9", size = 88815, upload-time = "2025-11-29T14:01:03.114Z" },
{ url = "https://files.pythonhosted.org/packages/c4/6d/934df738c87fb9617cabefe4891eece585a06abe6def25b4bca3b174429d/librt-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8983c5c06ac9c990eac5eb97a9f03fe41dc7e9d7993df74d9e8682a1056f596c", size = 90598, upload-time = "2025-11-29T14:01:04.071Z" },
{ url = "https://files.pythonhosted.org/packages/72/89/eeaa124f5e0f431c2b39119550378ae817a4b1a3c93fd7122f0639336fff/librt-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7769c579663a6f8dbf34878969ac71befa42067ce6bf78e6370bf0d1194997c", size = 88603, upload-time = "2025-11-29T14:01:05.02Z" },
{ url = "https://files.pythonhosted.org/packages/4d/ed/c60b3c1cfc27d709bc0288af428ce58543fcb5053cf3eadbc773c24257f5/librt-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d3c9a07eafdc70556f8c220da4a538e715668c0c63cabcc436a026e4e89950bf", size = 92112, upload-time = "2025-11-29T14:01:06.304Z" },
{ url = "https://files.pythonhosted.org/packages/c1/ab/f56169be5f716ef4ab0277be70bcb1874b4effc262e655d85b505af4884d/librt-0.6.3-cp312-cp312-win32.whl", hash = "sha256:38320386a48a15033da295df276aea93a92dfa94a862e06893f75ea1d8bbe89d", size = 20127, upload-time = "2025-11-29T14:01:07.283Z" },
{ url = "https://files.pythonhosted.org/packages/ff/8d/222750ce82bf95125529eaab585ac7e2829df252f3cfc05d68792fb1dd2c/librt-0.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:c0ecf4786ad0404b072196b5df774b1bb23c8aacdcacb6c10b4128bc7b00bd01", size = 21545, upload-time = "2025-11-29T14:01:08.184Z" },
{ url = "https://files.pythonhosted.org/packages/72/c9/f731ddcfb72f446a92a8674c6b8e1e2242773cce43a04f41549bd8b958ff/librt-0.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:9f2a6623057989ebc469cd9cc8fe436c40117a0147627568d03f84aef7854c55", size = 20946, upload-time = "2025-11-29T14:01:09.384Z" },
{ url = "https://files.pythonhosted.org/packages/dd/aa/3055dd440f8b8b3b7e8624539a0749dd8e1913e978993bcca9ce7e306231/librt-0.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e716f9012148a81f02f46a04fc4c663420c6fbfeacfac0b5e128cf43b4413d3", size = 27874, upload-time = "2025-11-29T14:01:10.615Z" },
{ url = "https://files.pythonhosted.org/packages/ef/93/226d7dd455eaa4c26712b5ccb2dfcca12831baa7f898c8ffd3a831e29fda/librt-0.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:669ff2495728009a96339c5ad2612569c6d8be4474e68f3f3ac85d7c3261f5f5", size = 27852, upload-time = "2025-11-29T14:01:11.535Z" },
{ url = "https://files.pythonhosted.org/packages/4e/8b/db9d51191aef4e4cc06285250affe0bb0ad8b2ed815f7ca77951655e6f02/librt-0.6.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:349b6873ebccfc24c9efd244e49da9f8a5c10f60f07575e248921aae2123fc42", size = 84264, upload-time = "2025-11-29T14:01:12.461Z" },
{ url = "https://files.pythonhosted.org/packages/8d/53/297c96bda3b5a73bdaf748f1e3ae757edd29a0a41a956b9c10379f193417/librt-0.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c74c26736008481c9f6d0adf1aedb5a52aff7361fea98276d1f965c0256ee70", size = 88432, upload-time = "2025-11-29T14:01:13.405Z" },
{ url = "https://files.pythonhosted.org/packages/54/3a/c005516071123278e340f22de72fa53d51e259d49215295c212da16c4dc2/librt-0.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:408a36ddc75e91918cb15b03460bdc8a015885025d67e68c6f78f08c3a88f522", size = 89014, upload-time = "2025-11-29T14:01:14.373Z" },
{ url = "https://files.pythonhosted.org/packages/8e/9b/ea715f818d926d17b94c80a12d81a79e95c44f52848e61e8ca1ff29bb9a9/librt-0.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e61ab234624c9ffca0248a707feffe6fac2343758a36725d8eb8a6efef0f8c30", size = 90807, upload-time = "2025-11-29T14:01:15.377Z" },
{ url = "https://files.pythonhosted.org/packages/f0/fc/4e2e4c87e002fa60917a8e474fd13c4bac9a759df82be3778573bb1ab954/librt-0.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:324462fe7e3896d592b967196512491ec60ca6e49c446fe59f40743d08c97917", size = 88890, upload-time = "2025-11-29T14:01:16.633Z" },
{ url = "https://files.pythonhosted.org/packages/70/7f/c7428734fbdfd4db3d5b9237fc3a857880b2ace66492836f6529fef25d92/librt-0.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36b2ec8c15030002c7f688b4863e7be42820d7c62d9c6eece3db54a2400f0530", size = 92300, upload-time = "2025-11-29T14:01:17.658Z" },
{ url = "https://files.pythonhosted.org/packages/f9/0c/738c4824fdfe74dc0f95d5e90ef9e759d4ecf7fd5ba964d54a7703322251/librt-0.6.3-cp313-cp313-win32.whl", hash = "sha256:25b1b60cb059471c0c0c803e07d0dfdc79e41a0a122f288b819219ed162672a3", size = 20159, upload-time = "2025-11-29T14:01:18.61Z" },
{ url = "https://files.pythonhosted.org/packages/f2/95/93d0e61bc617306ecf4c54636b5cbde4947d872563565c4abdd9d07a39d3/librt-0.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:10a95ad074e2a98c9e4abc7f5b7d40e5ecbfa84c04c6ab8a70fabf59bd429b88", size = 21484, upload-time = "2025-11-29T14:01:19.506Z" },
{ url = "https://files.pythonhosted.org/packages/10/23/abd7ace79ab54d1dbee265f13529266f686a7ce2d21ab59a992f989009b6/librt-0.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:17000df14f552e86877d67e4ab7966912224efc9368e998c96a6974a8d609bf9", size = 20935, upload-time = "2025-11-29T14:01:20.415Z" },
{ url = "https://files.pythonhosted.org/packages/83/14/c06cb31152182798ed98be73f54932ab984894f5a8fccf9b73130897a938/librt-0.6.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8e695f25d1a425ad7a272902af8ab8c8d66c1998b177e4b5f5e7b4e215d0c88a", size = 27566, upload-time = "2025-11-29T14:01:21.609Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b1/ce83ca7b057b06150519152f53a0b302d7c33c8692ce2f01f669b5a819d9/librt-0.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3e84a4121a7ae360ca4da436548a9c1ca8ca134a5ced76c893cc5944426164bd", size = 27753, upload-time = "2025-11-29T14:01:22.558Z" },
{ url = "https://files.pythonhosted.org/packages/3b/ec/739a885ef0a2839b6c25f1b01c99149d2cb6a34e933ffc8c051fcd22012e/librt-0.6.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:05f385a414de3f950886ea0aad8f109650d4b712cf9cc14cc17f5f62a9ab240b", size = 83178, upload-time = "2025-11-29T14:01:23.555Z" },
{ url = "https://files.pythonhosted.org/packages/db/bd/dc18bb1489d48c0911b9f4d72eae2d304ea264e215ba80f1e6ba4a9fc41d/librt-0.6.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36a8e337461150b05ca2c7bdedb9e591dfc262c5230422cea398e89d0c746cdc", size = 87266, upload-time = "2025-11-29T14:01:24.532Z" },
{ url = "https://files.pythonhosted.org/packages/94/f3/d0c5431b39eef15e48088b2d739ad84b17c2f1a22c0345c6d4c4a42b135e/librt-0.6.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcbe48f6a03979384f27086484dc2a14959be1613cb173458bd58f714f2c48f3", size = 87623, upload-time = "2025-11-29T14:01:25.798Z" },
{ url = "https://files.pythonhosted.org/packages/3b/15/9a52e90834e4bd6ee16cdbaf551cb32227cbaad27398391a189c489318bc/librt-0.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4bca9e4c260233fba37b15c4ec2f78aa99c1a79fbf902d19dd4a763c5c3fb751", size = 89436, upload-time = "2025-11-29T14:01:26.769Z" },
{ url = "https://files.pythonhosted.org/packages/c3/8a/a7e78e46e8486e023c50f21758930ef4793999115229afd65de69e94c9cc/librt-0.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:760c25ed6ac968e24803eb5f7deb17ce026902d39865e83036bacbf5cf242aa8", size = 87540, upload-time = "2025-11-29T14:01:27.756Z" },
{ url = "https://files.pythonhosted.org/packages/49/01/93799044a1cccac31f1074b07c583e181829d240539657e7f305ae63ae2a/librt-0.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4a93a353ccff20df6e34fa855ae8fd788832c88f40a9070e3ddd3356a9f0e", size = 90597, upload-time = "2025-11-29T14:01:29.35Z" },
{ url = "https://files.pythonhosted.org/packages/a7/29/00c7f58b8f8eb1bad6529ffb6c9cdcc0890a27dac59ecda04f817ead5277/librt-0.6.3-cp314-cp314-win32.whl", hash = "sha256:cb92741c2b4ea63c09609b064b26f7f5d9032b61ae222558c55832ec3ad0bcaf", size = 18955, upload-time = "2025-11-29T14:01:30.325Z" },
{ url = "https://files.pythonhosted.org/packages/d7/13/2739e6e197a9f751375a37908a6a5b0bff637b81338497a1bcb5817394da/librt-0.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:fdcd095b1b812d756fa5452aca93b962cf620694c0cadb192cec2bb77dcca9a2", size = 20263, upload-time = "2025-11-29T14:01:31.287Z" },
{ url = "https://files.pythonhosted.org/packages/e1/73/393868fc2158705ea003114a24e73bb10b03bda31e9ad7b5c5ec6575338b/librt-0.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:822ca79e28720a76a935c228d37da6579edef048a17cd98d406a2484d10eda78", size = 19575, upload-time = "2025-11-29T14:01:32.229Z" },
{ url = "https://files.pythonhosted.org/packages/48/6d/3c8ff3dec21bf804a205286dd63fd28dcdbe00b8dd7eb7ccf2e21a40a0b0/librt-0.6.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:078cd77064d1640cb7b0650871a772956066174d92c8aeda188a489b58495179", size = 28732, upload-time = "2025-11-29T14:01:33.165Z" },
{ url = "https://files.pythonhosted.org/packages/f4/90/e214b8b4aa34ed3d3f1040719c06c4d22472c40c5ef81a922d5af7876eb4/librt-0.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5cc22f7f5c0cc50ed69f4b15b9c51d602aabc4500b433aaa2ddd29e578f452f7", size = 29065, upload-time = "2025-11-29T14:01:34.088Z" },
{ url = "https://files.pythonhosted.org/packages/ab/90/ef61ed51f0a7770cc703422d907a757bbd8811ce820c333d3db2fd13542a/librt-0.6.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:14b345eb7afb61b9fdcdfda6738946bd11b8e0f6be258666b0646af3b9bb5916", size = 93703, upload-time = "2025-11-29T14:01:35.057Z" },
{ url = "https://files.pythonhosted.org/packages/a8/ae/c30bb119c35962cbe9a908a71da99c168056fc3f6e9bbcbc157d0b724d89/librt-0.6.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d46aa46aa29b067f0b8b84f448fd9719aaf5f4c621cc279164d76a9dc9ab3e8", size = 98890, upload-time = "2025-11-29T14:01:36.031Z" },
{ url = "https://files.pythonhosted.org/packages/d1/96/47a4a78d252d36f072b79d592df10600d379a895c3880c8cbd2ac699f0ad/librt-0.6.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b51ba7d9d5d9001494769eca8c0988adce25d0a970c3ba3f2eb9df9d08036fc", size = 98255, upload-time = "2025-11-29T14:01:37.058Z" },
{ url = "https://files.pythonhosted.org/packages/e5/28/779b5cc3cd9987683884eb5f5672e3251676bebaaae6b7da1cf366eb1da1/librt-0.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ced0925a18fddcff289ef54386b2fc230c5af3c83b11558571124bfc485b8c07", size = 100769, upload-time = "2025-11-29T14:01:38.413Z" },
{ url = "https://files.pythonhosted.org/packages/28/d7/771755e57c375cb9d25a4e106f570607fd856e2cb91b02418db1db954796/librt-0.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6bac97e51f66da2ca012adddbe9fd656b17f7368d439de30898f24b39512f40f", size = 98580, upload-time = "2025-11-29T14:01:39.459Z" },
{ url = "https://files.pythonhosted.org/packages/d0/ec/8b157eb8fbc066339a2f34b0aceb2028097d0ed6150a52e23284a311eafe/librt-0.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b2922a0e8fa97395553c304edc3bd36168d8eeec26b92478e292e5d4445c1ef0", size = 101706, upload-time = "2025-11-29T14:01:40.474Z" },
{ url = "https://files.pythonhosted.org/packages/82/a8/4aaead9a06c795a318282aebf7d3e3e578fa889ff396e1b640c3be4c7806/librt-0.6.3-cp314-cp314t-win32.whl", hash = "sha256:f33462b19503ba68d80dac8a1354402675849259fb3ebf53b67de86421735a3a", size = 19465, upload-time = "2025-11-29T14:01:41.77Z" },
{ url = "https://files.pythonhosted.org/packages/3a/61/b7e6a02746c1731670c19ba07d86da90b1ae45d29e405c0b5615abf97cde/librt-0.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:04f8ce401d4f6380cfc42af0f4e67342bf34c820dae01343f58f472dbac75dcf", size = 21042, upload-time = "2025-11-29T14:01:42.865Z" },
{ url = "https://files.pythonhosted.org/packages/0e/3d/72cc9ec90bb80b5b1a65f0bb74a0f540195837baaf3b98c7fa4a7aa9718e/librt-0.6.3-cp314-cp314t-win_arm64.whl", hash = "sha256:afb39550205cc5e5c935762c6bf6a2bb34f7d21a68eadb25e2db7bf3593fecc0", size = 20246, upload-time = "2025-11-29T14:01:44.13Z" },
]
[[package]]
name = "mypy"
version = "1.19.0"
version = "1.18.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "librt" },
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" },
{ url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" },
{ url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" },
{ url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" },
{ url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" },
{ url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" },
{ url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" },
{ url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" },
{ url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" },
{ url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" },
{ url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" },
{ url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" },
{ url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" },
{ url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" },
{ url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" },
{ url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" },
{ url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" },
{ url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" },
{ url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" },
{ url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" },
{ url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" },
{ url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" },
{ url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" },
{ url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" },
{ url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" },
{ url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" },
{ url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" },
{ url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" },
{ url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" },
{ url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" },
{ url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" },
{ url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" },
{ url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" },
{ url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" },
{ url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" },
{ url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" },
{ url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" },
{ url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" },
{ url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" },
{ url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" },
{ url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" },
{ url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" },
{ url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" },
{ url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" },
{ url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" },
{ url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" },
{ url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" },
{ url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" },
{ url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" },
{ url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" },
{ url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" },
{ url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" },
{ url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" },
{ url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" },
{ url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" },
{ url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" },
{ url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" },
]
[[package]]
@@ -440,7 +359,7 @@ wheels = [
[[package]]
name = "pydantic"
version = "2.12.5"
version = "2.12.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -448,9 +367,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
{ url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" },
]
[[package]]
@@ -638,28 +557,28 @@ sdist = { url = "https://files.pythonhosted.org/packages/36/47/ab65fc1d682befc31
[[package]]
name = "ruff"
version = "0.14.7"
version = "0.14.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b7/5b/dd7406afa6c95e3d8fa9d652b6d6dd17dd4a6bf63cb477014e8ccd3dcd46/ruff-0.14.7.tar.gz", hash = "sha256:3417deb75d23bd14a722b57b0a1435561db65f0ad97435b4cf9f85ffcef34ae5", size = 5727324, upload-time = "2025-11-28T20:55:10.525Z" }
sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/b1/7ea5647aaf90106f6d102230e5df874613da43d1089864da1553b899ba5e/ruff-0.14.7-py3-none-linux_armv6l.whl", hash = "sha256:b9d5cb5a176c7236892ad7224bc1e63902e4842c460a0b5210701b13e3de4fca", size = 13414475, upload-time = "2025-11-28T20:54:54.569Z" },
{ url = "https://files.pythonhosted.org/packages/af/19/fddb4cd532299db9cdaf0efdc20f5c573ce9952a11cb532d3b859d6d9871/ruff-0.14.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3f64fe375aefaf36ca7d7250292141e39b4cea8250427482ae779a2aa5d90015", size = 13634613, upload-time = "2025-11-28T20:55:17.54Z" },
{ url = "https://files.pythonhosted.org/packages/40/2b/469a66e821d4f3de0440676ed3e04b8e2a1dc7575cf6fa3ba6d55e3c8557/ruff-0.14.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93e83bd3a9e1a3bda64cb771c0d47cda0e0d148165013ae2d3554d718632d554", size = 12765458, upload-time = "2025-11-28T20:55:26.128Z" },
{ url = "https://files.pythonhosted.org/packages/f1/05/0b001f734fe550bcfde4ce845948ac620ff908ab7241a39a1b39bb3c5f49/ruff-0.14.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3838948e3facc59a6070795de2ae16e5786861850f78d5914a03f12659e88f94", size = 13236412, upload-time = "2025-11-28T20:55:28.602Z" },
{ url = "https://files.pythonhosted.org/packages/11/36/8ed15d243f011b4e5da75cd56d6131c6766f55334d14ba31cce5461f28aa/ruff-0.14.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24c8487194d38b6d71cd0fd17a5b6715cda29f59baca1defe1e3a03240f851d1", size = 13182949, upload-time = "2025-11-28T20:55:33.265Z" },
{ url = "https://files.pythonhosted.org/packages/3b/cf/fcb0b5a195455729834f2a6eadfe2e4519d8ca08c74f6d2b564a4f18f553/ruff-0.14.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79c73db6833f058a4be8ffe4a0913b6d4ad41f6324745179bd2aa09275b01d0b", size = 13816470, upload-time = "2025-11-28T20:55:08.203Z" },
{ url = "https://files.pythonhosted.org/packages/7f/5d/34a4748577ff7a5ed2f2471456740f02e86d1568a18c9faccfc73bd9ca3f/ruff-0.14.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:12eb7014fccff10fc62d15c79d8a6be4d0c2d60fe3f8e4d169a0d2def75f5dad", size = 15289621, upload-time = "2025-11-28T20:55:30.837Z" },
{ url = "https://files.pythonhosted.org/packages/53/53/0a9385f047a858ba133d96f3f8e3c9c66a31cc7c4b445368ef88ebeac209/ruff-0.14.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c623bbdc902de7ff715a93fa3bb377a4e42dd696937bf95669118773dbf0c50", size = 14975817, upload-time = "2025-11-28T20:55:24.107Z" },
{ url = "https://files.pythonhosted.org/packages/a8/d7/2f1c32af54c3b46e7fadbf8006d8b9bcfbea535c316b0bd8813d6fb25e5d/ruff-0.14.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f53accc02ed2d200fa621593cdb3c1ae06aa9b2c3cae70bc96f72f0000ae97a9", size = 14284549, upload-time = "2025-11-28T20:55:06.08Z" },
{ url = "https://files.pythonhosted.org/packages/92/05/434ddd86becd64629c25fb6b4ce7637dd52a45cc4a4415a3008fe61c27b9/ruff-0.14.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:281f0e61a23fcdcffca210591f0f53aafaa15f9025b5b3f9706879aaa8683bc4", size = 14071389, upload-time = "2025-11-28T20:55:35.617Z" },
{ url = "https://files.pythonhosted.org/packages/ff/50/fdf89d4d80f7f9d4f420d26089a79b3bb1538fe44586b148451bc2ba8d9c/ruff-0.14.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:dbbaa5e14148965b91cb090236931182ee522a5fac9bc5575bafc5c07b9f9682", size = 14202679, upload-time = "2025-11-28T20:55:01.472Z" },
{ url = "https://files.pythonhosted.org/packages/77/54/87b34988984555425ce967f08a36df0ebd339bb5d9d0e92a47e41151eafc/ruff-0.14.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1464b6e54880c0fe2f2d6eaefb6db15373331414eddf89d6b903767ae2458143", size = 13147677, upload-time = "2025-11-28T20:55:19.933Z" },
{ url = "https://files.pythonhosted.org/packages/67/29/f55e4d44edfe053918a16a3299e758e1c18eef216b7a7092550d7a9ec51c/ruff-0.14.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f217ed871e4621ea6128460df57b19ce0580606c23aeab50f5de425d05226784", size = 13151392, upload-time = "2025-11-28T20:55:21.967Z" },
{ url = "https://files.pythonhosted.org/packages/36/69/47aae6dbd4f1d9b4f7085f4d9dcc84e04561ee7ad067bf52e0f9b02e3209/ruff-0.14.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6be02e849440ed3602d2eb478ff7ff07d53e3758f7948a2a598829660988619e", size = 13412230, upload-time = "2025-11-28T20:55:12.749Z" },
{ url = "https://files.pythonhosted.org/packages/b7/4b/6e96cb6ba297f2ba502a231cd732ed7c3de98b1a896671b932a5eefa3804/ruff-0.14.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19a0f116ee5e2b468dfe80c41c84e2bbd6b74f7b719bee86c2ecde0a34563bcc", size = 14195397, upload-time = "2025-11-28T20:54:56.896Z" },
{ url = "https://files.pythonhosted.org/packages/69/82/251d5f1aa4dcad30aed491b4657cecd9fb4274214da6960ffec144c260f7/ruff-0.14.7-py3-none-win32.whl", hash = "sha256:e33052c9199b347c8937937163b9b149ef6ab2e4bb37b042e593da2e6f6cccfa", size = 13126751, upload-time = "2025-11-28T20:55:03.47Z" },
{ url = "https://files.pythonhosted.org/packages/a8/b5/d0b7d145963136b564806f6584647af45ab98946660d399ec4da79cae036/ruff-0.14.7-py3-none-win_amd64.whl", hash = "sha256:e17a20ad0d3fad47a326d773a042b924d3ac31c6ca6deb6c72e9e6b5f661a7c6", size = 14531726, upload-time = "2025-11-28T20:54:59.121Z" },
{ url = "https://files.pythonhosted.org/packages/1d/d2/1637f4360ada6a368d3265bf39f2cf737a0aaab15ab520fc005903e883f8/ruff-0.14.7-py3-none-win_arm64.whl", hash = "sha256:be4d653d3bea1b19742fcc6502354e32f65cd61ff2fbdb365803ef2c2aec6228", size = 13609215, upload-time = "2025-11-28T20:55:15.375Z" },
{ url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" },
{ url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" },
{ url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" },
{ url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" },
{ url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" },
{ url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" },
{ url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" },
{ url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" },
{ url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" },
{ url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" },
{ url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" },
{ url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" },
{ url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" },
{ url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" },
{ url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" },
{ url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" },
{ url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" },
{ url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" },
]
[[package]]
@@ -671,19 +590,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "starlette"
version = "0.50.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
]
[[package]]
name = "tomli"
version = "2.3.0"
@@ -733,31 +639,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
]
[[package]]
name = "ty"
version = "0.0.1a27"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8f/65/3592d7c73d80664378fc90d0a00c33449a99cbf13b984433c883815245f3/ty-0.0.1a27.tar.gz", hash = "sha256:d34fe04979f2c912700cbf0919e8f9b4eeaa10c4a2aff7450e5e4c90f998bc28", size = 4516059, upload-time = "2025-11-18T21:55:18.381Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/05/7945aa97356446fd53ed3ddc7ee02a88d8ad394217acd9428f472d6b109d/ty-0.0.1a27-py3-none-linux_armv6l.whl", hash = "sha256:3cbb735f5ecb3a7a5f5b82fb24da17912788c109086df4e97d454c8fb236fbc5", size = 9375047, upload-time = "2025-11-18T21:54:31.577Z" },
{ url = "https://files.pythonhosted.org/packages/69/4e/89b167a03de0e9ec329dc89bc02e8694768e4576337ef6c0699987681342/ty-0.0.1a27-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a6367236dc456ba2416563301d498aef8c6f8959be88777ef7ba5ac1bf15f0b", size = 9169540, upload-time = "2025-11-18T21:54:34.036Z" },
{ url = "https://files.pythonhosted.org/packages/38/07/e62009ab9cc242e1becb2bd992097c80a133fce0d4f055fba6576150d08a/ty-0.0.1a27-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8e93e231a1bcde964cdb062d2d5e549c24493fb1638eecae8fcc42b81e9463a4", size = 8711942, upload-time = "2025-11-18T21:54:36.3Z" },
{ url = "https://files.pythonhosted.org/packages/b5/43/f35716ec15406f13085db52e762a3cc663c651531a8124481d0ba602eca0/ty-0.0.1a27-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b6a8166b60117da1179851a3d719cc798bf7e61f91b35d76242f0059e9ae1d", size = 8984208, upload-time = "2025-11-18T21:54:39.453Z" },
{ url = "https://files.pythonhosted.org/packages/2d/79/486a3374809523172379768de882c7a369861165802990177fe81489b85f/ty-0.0.1a27-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfbe8b0e831c072b79a078d6c126d7f4d48ca17f64a103de1b93aeda32265dc5", size = 9157209, upload-time = "2025-11-18T21:54:42.664Z" },
{ url = "https://files.pythonhosted.org/packages/ff/08/9a7c8efcb327197d7d347c548850ef4b54de1c254981b65e8cd0672dc327/ty-0.0.1a27-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90e09678331552e7c25d7eb47868b0910dc5b9b212ae22c8ce71a52d6576ddbb", size = 9519207, upload-time = "2025-11-18T21:54:45.311Z" },
{ url = "https://files.pythonhosted.org/packages/e0/9d/7b4680683e83204b9edec551bb91c21c789ebc586b949c5218157ee474b7/ty-0.0.1a27-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:88c03e4beeca79d85a5618921e44b3a6ea957e0453e08b1cdd418b51da645939", size = 10148794, upload-time = "2025-11-18T21:54:48.329Z" },
{ url = "https://files.pythonhosted.org/packages/89/21/8b961b0ab00c28223f06b33222427a8e31aa04f39d1b236acc93021c626c/ty-0.0.1a27-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ece5811322789fefe22fc088ed36c5879489cd39e913f9c1ff2a7678f089c61", size = 9900563, upload-time = "2025-11-18T21:54:51.214Z" },
{ url = "https://files.pythonhosted.org/packages/85/eb/95e1f0b426c2ea8d443aa923fcab509059c467bbe64a15baaf573fea1203/ty-0.0.1a27-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f2ccb4f0fddcd6e2017c268dfce2489e9a36cb82a5900afe6425835248b1086", size = 9926355, upload-time = "2025-11-18T21:54:53.927Z" },
{ url = "https://files.pythonhosted.org/packages/f5/78/40e7f072049e63c414f2845df780be3a494d92198c87c2ffa65e63aecf3f/ty-0.0.1a27-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33450528312e41d003e96a1647780b2783ab7569bbc29c04fc76f2d1908061e3", size = 9480580, upload-time = "2025-11-18T21:54:56.617Z" },
{ url = "https://files.pythonhosted.org/packages/18/da/f4a2dfedab39096808ddf7475f35ceb750d9a9da840bee4afd47b871742f/ty-0.0.1a27-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0a9ac635deaa2b15947701197ede40cdecd13f89f19351872d16f9ccd773fa1", size = 8957524, upload-time = "2025-11-18T21:54:59.085Z" },
{ url = "https://files.pythonhosted.org/packages/21/ea/26fee9a20cf77a157316fd3ab9c6db8ad5a0b20b2d38a43f3452622587ac/ty-0.0.1a27-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:797fb2cd49b6b9b3ac9f2f0e401fb02d3aa155badc05a8591d048d38d28f1e0c", size = 9201098, upload-time = "2025-11-18T21:55:01.845Z" },
{ url = "https://files.pythonhosted.org/packages/b0/53/e14591d1275108c9ae28f97ac5d4b93adcc2c8a4b1b9a880dfa9d07c15f8/ty-0.0.1a27-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7fe81679a0941f85e98187d444604e24b15bde0a85874957c945751756314d03", size = 9275470, upload-time = "2025-11-18T21:55:04.23Z" },
{ url = "https://files.pythonhosted.org/packages/37/44/e2c9acecac70bf06fb41de285e7be2433c2c9828f71e3bf0e886fc85c4fd/ty-0.0.1a27-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:355f651d0cdb85535a82bd9f0583f77b28e3fd7bba7b7da33dcee5a576eff28b", size = 9592394, upload-time = "2025-11-18T21:55:06.542Z" },
{ url = "https://files.pythonhosted.org/packages/ee/a7/4636369731b24ed07c2b4c7805b8d990283d677180662c532d82e4ef1a36/ty-0.0.1a27-py3-none-win32.whl", hash = "sha256:61782e5f40e6df622093847b34c366634b75d53f839986f1bf4481672ad6cb55", size = 8783816, upload-time = "2025-11-18T21:55:09.648Z" },
{ url = "https://files.pythonhosted.org/packages/a7/1d/b76487725628d9e81d9047dc0033a5e167e0d10f27893d04de67fe1a9763/ty-0.0.1a27-py3-none-win_amd64.whl", hash = "sha256:c682b238085d3191acddcf66ef22641562946b1bba2a7f316012d5b2a2f4de11", size = 9616833, upload-time = "2025-11-18T21:55:12.457Z" },
{ url = "https://files.pythonhosted.org/packages/3a/db/c7cd5276c8f336a3cf87992b75ba9d486a7cf54e753fcd42495b3bc56fb7/ty-0.0.1a27-py3-none-win_arm64.whl", hash = "sha256:e146dfa32cbb0ac6afb0cb65659e87e4e313715e68d76fe5ae0a4b3d5b912ce8", size = 9137796, upload-time = "2025-11-18T21:55:15.897Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"