diff --git a/backend/infrahub/artifacts/models.py b/backend/infrahub/artifacts/models.py index 7e80255932..f0b1093c7d 100644 --- a/backend/infrahub/artifacts/models.py +++ b/backend/infrahub/artifacts/models.py @@ -25,7 +25,8 @@ class CheckArtifactCreate(BaseModel): target_kind: str = Field(..., description="The kind of the target object for this artifact") target_name: str = Field(..., description="Name of the artifact target") artifact_id: str | None = Field(default=None, description="The id of the artifact if it previously existed") - query: str = Field(..., description="The name of the query to use when collecting data") + query: str = Field(..., description="The name of the query to use when collecting data") # Deprecated + query_id: str = Field(..., description="The id of the query to use when collecting data") timeout: int = Field(..., description="Timeout for requests used to generate this artifact") variables: dict = Field(..., description="Input variables when generating the artifact") validator_id: str = Field(..., description="The ID of the validator") diff --git a/backend/infrahub/computed_attribute/tasks.py b/backend/infrahub/computed_attribute/tasks.py index 689c78d53e..d4f2cd0b04 100644 --- a/backend/infrahub/computed_attribute/tasks.py +++ b/backend/infrahub/computed_attribute/tasks.py @@ -104,7 +104,7 @@ async def process_transform( ) # type: ignore[misc] data = await client.query_gql_query( - name=transform.query.peer.name.value, + name=transform.query.id, branch_name=branch_name, variables={"id": object_id}, update_group=True, diff --git a/backend/infrahub/core/schema/manager.py b/backend/infrahub/core/schema/manager.py index 29820c3e89..271f778dab 100644 --- a/backend/infrahub/core/schema/manager.py +++ b/backend/infrahub/core/schema/manager.py @@ -2,6 +2,9 @@ from typing import TYPE_CHECKING, Any +from cachetools import LRUCache +from infrahub_sdk.schema import BranchSchema as SDKBranchSchema + from infrahub import lock from infrahub.core.manager import NodeManager from infrahub.core.models import ( @@ -40,6 +43,8 @@ class SchemaManager(NodeManager): def __init__(self) -> None: self._cache: dict[int, Any] = {} self._branches: dict[str, SchemaBranch] = {} + self._branch_hash_by_name: dict[str, str] = {} + self._sdk_branches: LRUCache[str, SDKBranchSchema] = LRUCache(maxsize=10) def _get_from_cache(self, key: int) -> Any: return self._cache[key] @@ -140,12 +145,23 @@ def get_schema_branch(self, name: str) -> SchemaBranch: if name in self._branches: return self._branches[name] - self._branches[name] = SchemaBranch(cache=self._cache, name=name) + self.set_schema_branch(name, schema=SchemaBranch(cache=self._cache, name=name)) return self._branches[name] + def get_sdk_schema_branch(self, name: str) -> SDKBranchSchema: + schema_hash = self._branch_hash_by_name[name] + branch_schema = self._sdk_branches.get(schema_hash) + if not branch_schema: + self._sdk_branches[schema_hash] = SDKBranchSchema.from_api_response( + data=self._branches[name].to_dict_api_schema_object() + ) + + return self._sdk_branches[schema_hash] + def set_schema_branch(self, name: str, schema: SchemaBranch) -> None: schema.name = name self._branches[name] = schema + self._branch_hash_by_name[name] = schema.get_hash() def process_schema_branch(self, name: str) -> None: schema_branch = self.get_schema_branch(name=name) @@ -764,6 +780,8 @@ def purge_inactive_branches(self, active_branches: list[str]) -> list[str]: for branch_name in list(self._branches.keys()): if branch_name not in active_branches: del self._branches[branch_name] + if branch_name in self._branch_hash_by_name: + del self._branch_hash_by_name[branch_name] removed_branches.append(branch_name) for hash_key in list(self._cache.keys()): diff --git a/backend/infrahub/core/schema/schema_branch.py b/backend/infrahub/core/schema/schema_branch.py index 7427e5e9c6..1a6573b93d 100644 --- a/backend/infrahub/core/schema/schema_branch.py +++ b/backend/infrahub/core/schema/schema_branch.py @@ -162,6 +162,14 @@ def to_dict_schema_object(self, duplicate: bool = False) -> dict[str, dict[str, "templates": {name: self.get(name, duplicate=duplicate) for name in self.templates}, } + def to_dict_api_schema_object(self) -> dict[str, list[dict]]: + return { + "nodes": [self.get(name, duplicate=False).model_dump() for name in self.nodes], + "profiles": [self.get(name, duplicate=False).model_dump() for name in self.profiles], + "generics": [self.get(name, duplicate=False).model_dump() for name in self.generics], + "templates": [self.get(name, duplicate=False).model_dump() for name in self.templates], + } + @classmethod def from_dict_schema_object(cls, data: dict) -> Self: type_mapping = { diff --git a/backend/infrahub/generators/tasks.py b/backend/infrahub/generators/tasks.py index 715f4aa14c..11039627f0 100644 --- a/backend/infrahub/generators/tasks.py +++ b/backend/infrahub/generators/tasks.py @@ -21,6 +21,7 @@ ) from infrahub.git.base import extract_repo_file_information from infrahub.git.repository import get_initialized_repo +from infrahub.git.utils import fetch_proposed_change_generator_definition_targets from infrahub.workers.dependencies import get_client, get_workflow from infrahub.workflows.catalogue import REQUEST_GENERATOR_DEFINITION_RUN, REQUEST_GENERATOR_RUN from infrahub.workflows.utils import add_tags @@ -177,14 +178,9 @@ async def request_generator_definition_run( branch=model.branch, ) - group = await client.get( - kind=InfrahubKind.GENERICGROUP, - prefetch_relationships=True, - populate_store=True, - id=model.generator_definition.group_id, - branch=model.branch, + group = await fetch_proposed_change_generator_definition_targets( + client=client, branch=model.branch, definition=model.generator_definition ) - await group.members.fetch() instance_by_member = {} for instance in existing_instances: diff --git a/backend/infrahub/git/integrator.py b/backend/infrahub/git/integrator.py index 9d5d17de53..5e3acec6f1 100644 --- a/backend/infrahub/git/integrator.py +++ b/backend/infrahub/git/integrator.py @@ -1363,7 +1363,7 @@ async def render_artifact( message: CheckArtifactCreate | RequestArtifactGenerate, ) -> ArtifactGenerateResult: response = await self.sdk.query_gql_query( - name=message.query, + name=message.query_id, variables=message.variables, update_group=True, subscribers=[artifact.id], diff --git a/backend/infrahub/git/models.py b/backend/infrahub/git/models.py index eb19297292..3d58744314 100644 --- a/backend/infrahub/git/models.py +++ b/backend/infrahub/git/models.py @@ -38,7 +38,8 @@ class RequestArtifactGenerate(BaseModel): target_kind: str = Field(..., description="The kind of the target object for this artifact") target_name: str = Field(..., description="Name of the artifact target") artifact_id: str | None = Field(default=None, description="The id of the artifact if it previously existed") - query: str = Field(..., description="The name of the query to use when collecting data") + query: str = Field(..., description="The name of the query to use when collecting data") # Deprecated + query_id: str = Field(..., description="The id of the query to use when collecting data") timeout: int = Field(..., description="Timeout for requests used to generate this artifact") variables: dict = Field(..., description="Input variables when generating the artifact") context: InfrahubContext = Field(..., description="The context of the task") diff --git a/backend/infrahub/git/repository.py b/backend/infrahub/git/repository.py index 61af3c3d98..78448dd64b 100644 --- a/backend/infrahub/git/repository.py +++ b/backend/infrahub/git/repository.py @@ -2,6 +2,9 @@ from typing import TYPE_CHECKING, Any +from cachetools import TTLCache +from cachetools.keys import hashkey +from cachetools_async import cached from git.exc import BadName, GitCommandError from infrahub_sdk.exceptions import GraphQLError from prefect import task @@ -248,12 +251,13 @@ async def sync_from_remote(self, commit: str | None = None) -> None: await self.update_commit_value(branch_name=self.infrahub_branch_name, commit=commit) -@task( - name="Fetch repository commit", - description="Retrieve a git repository at a given commit, if it does not already exist locally", - cache_policy=NONE, +@cached( + TTLCache(maxsize=100, ttl=30), + key=lambda *_, **kwargs: hashkey( + kwargs.get("repository_id"), kwargs.get("name"), kwargs.get("repository_kind"), kwargs.get("commit") + ), ) -async def get_initialized_repo( +async def _get_initialized_repo( client: InfrahubClient, repository_id: str, name: str, repository_kind: str, commit: str | None = None ) -> InfrahubReadOnlyRepository | InfrahubRepository: if repository_kind == InfrahubKind.REPOSITORY: @@ -263,3 +267,16 @@ async def get_initialized_repo( return await InfrahubReadOnlyRepository.init(id=repository_id, name=name, commit=commit, client=client) raise NotImplementedError(f"The repository kind {repository_kind} has not been implemented") + + +@task( + name="Fetch repository commit", + description="Retrieve a git repository at a given commit, if it does not already exist locally", + cache_policy=NONE, +) +async def get_initialized_repo( + client: InfrahubClient, repository_id: str, name: str, repository_kind: str, commit: str | None = None +) -> InfrahubReadOnlyRepository | InfrahubRepository: + return await _get_initialized_repo( + client=client, repository_id=repository_id, name=name, repository_kind=repository_kind, commit=commit + ) diff --git a/backend/infrahub/git/tasks.py b/backend/infrahub/git/tasks.py index a090f709bf..0b20b68018 100644 --- a/backend/infrahub/git/tasks.py +++ b/backend/infrahub/git/tasks.py @@ -53,6 +53,7 @@ UserCheckDefinitionData, ) from .repository import InfrahubReadOnlyRepository, InfrahubRepository, get_initialized_repo +from .utils import fetch_artifact_definition_targets, fetch_check_definition_targets @flow( @@ -323,9 +324,8 @@ async def generate_request_artifact_definition( kind=CoreArtifactDefinition, id=model.artifact_definition_id, branch=model.branch ) - await artifact_definition.targets.fetch() - group = artifact_definition.targets.peer - await group.members.fetch() + group = await fetch_artifact_definition_targets(client=client, branch=model.branch, definition=artifact_definition) + current_members = [member.id for member in group.members.peers] artifacts_by_member = {} @@ -356,6 +356,7 @@ async def generate_request_artifact_definition( transform_location = f"{transform.file_path.value}::{transform.class_name.value}" convert_query_response = transform.convert_query_response.value + batch = await client.create_batch() for relationship in group.members.peers: member = relationship.peer artifact_id = artifacts_by_member.get(member.id) @@ -376,6 +377,7 @@ async def generate_request_artifact_definition( repository_kind=repository.get_kind(), branch_name=model.branch, query=query.name.value, + query_id=query.id, variables=await member.extract(params=artifact_definition.parameters.value), target_id=member.id, target_name=member.display_label, @@ -385,10 +387,16 @@ async def generate_request_artifact_definition( context=context, ) - await get_workflow().submit_workflow( - workflow=REQUEST_ARTIFACT_GENERATE, context=context, parameters={"model": request_artifact_generate_model} + batch.add( + task=get_workflow().submit_workflow, + workflow=REQUEST_ARTIFACT_GENERATE, + context=context, + parameters={"model": request_artifact_generate_model}, ) + async for _, _ in batch.execute(): + pass + @flow(name="git-repository-pull-read-only", flow_run_name="Pull latest commit on {model.repository_name}") async def pull_read_only(model: GitRepositoryPullReadOnly) -> None: @@ -569,9 +577,7 @@ async def trigger_repository_user_checks_definitions(model: UserCheckDefinitionD if definition.targets.id: # Check against a group of targets - await definition.targets.fetch() - group = definition.targets.peer - await group.members.fetch() + group = await fetch_check_definition_targets(client=client, branch=model.branch_name, definition=definition) check_models = [] for relationship in group.members.peers: member = relationship.peer diff --git a/backend/infrahub/git/utils.py b/backend/infrahub/git/utils.py index 2b6d131021..2e9ed158d7 100644 --- a/backend/infrahub/git/utils.py +++ b/backend/infrahub/git/utils.py @@ -1,9 +1,16 @@ -from typing import TYPE_CHECKING +from collections import defaultdict +from typing import TYPE_CHECKING, Any + +from infrahub_sdk import InfrahubClient +from infrahub_sdk.node import RelationshipManager +from infrahub_sdk.protocols import CoreArtifactDefinition, CoreCheckDefinition, CoreGroup +from infrahub_sdk.types import Order from infrahub.core import registry from infrahub.core.constants import InfrahubKind from infrahub.core.manager import NodeManager from infrahub.database import InfrahubDatabase +from infrahub.generators.models import ProposedChangeGeneratorDefinition from .models import RepositoryBranchInfo, RepositoryData @@ -46,3 +53,118 @@ async def get_repositories_commit_per_branch( ) return repositories + + +def _collect_parameter_first_segments(params: Any) -> set[str]: + segments: set[str] = set() + + def _walk(value: Any) -> None: + if isinstance(value, str): + segment = value.split("__", 1)[0] + if segment: + segments.add(segment) + elif isinstance(value, dict): + for nested in value.values(): + _walk(nested) + elif isinstance(value, (list, tuple, set)): + for nested in value: + _walk(nested) + + _walk(params) + return segments + + +async def _prefetch_group_member_nodes( + client: InfrahubClient, + members: RelationshipManager, + branch: str, + required_fields: set[str], +) -> None: + ids_per_kind: dict[str, set[str]] = defaultdict(set) + for peer in members.peers: + if peer.id and peer.typename: + ids_per_kind[peer.typename].add(peer.id) + + if not ids_per_kind: + return + + batch = await client.create_batch() + + for kind, ids in ids_per_kind.items(): + schema = await client.schema.get(kind=kind, branch=branch) + + # FIXME: https://github.com/opsmill/infrahub-sdk-python/pull/205 + valid_fields = set(schema.attribute_names) | set(schema.relationship_names) + keep_relationships = set(schema.relationship_names) & required_fields + cleaned_fields = valid_fields - required_fields + + kwargs: dict[str, Any] = { + "kind": kind, + "ids": list(ids), + "branch": branch, + "exclude": list(cleaned_fields), + "populate_store": True, + "order": Order(disable=True), + } + + if keep_relationships: + kwargs["include"] = list(keep_relationships) + + batch.add(task=client.filters, **kwargs) + + async for _ in batch.execute(): + pass + + +async def _fetch_definition_targets( + client: InfrahubClient, + branch: str, + group_id: str, + parameters: Any, +) -> CoreGroup: + group = await client.get( + kind=CoreGroup, + id=group_id, + branch=branch, + include=["members"], + ) + + parameter_fields = _collect_parameter_first_segments(parameters) + await _prefetch_group_member_nodes( + client=client, + members=group.members, + branch=branch, + required_fields=parameter_fields, + ) + + return group + + +async def fetch_artifact_definition_targets( + client: InfrahubClient, + branch: str, + definition: CoreArtifactDefinition, +) -> CoreGroup: + return await _fetch_definition_targets( + client=client, branch=branch, group_id=definition.targets.id, parameters=definition.parameters.value + ) + + +async def fetch_check_definition_targets( + client: InfrahubClient, + branch: str, + definition: CoreCheckDefinition, +) -> CoreGroup: + return await _fetch_definition_targets( + client=client, branch=branch, group_id=definition.targets.id, parameters=definition.parameters.value + ) + + +async def fetch_proposed_change_generator_definition_targets( + client: InfrahubClient, + branch: str, + definition: ProposedChangeGeneratorDefinition, +) -> CoreGroup: + return await _fetch_definition_targets( + client=client, branch=branch, group_id=definition.group_id, parameters=definition.parameters + ) diff --git a/backend/infrahub/message_bus/types.py b/backend/infrahub/message_bus/types.py index 4e7db4107e..c25185e74f 100644 --- a/backend/infrahub/message_bus/types.py +++ b/backend/infrahub/message_bus/types.py @@ -89,7 +89,8 @@ class ProposedChangeArtifactDefinition(BaseModel): definition_id: str definition_name: str artifact_name: str - query_name: str + query_name: str # Deprecated + query_id: str query_models: list[str] repository_id: str transform_kind: str diff --git a/backend/infrahub/proposed_change/tasks.py b/backend/infrahub/proposed_change/tasks.py index 72a18a9a44..c3bb2ac2ef 100644 --- a/backend/infrahub/proposed_change/tasks.py +++ b/backend/infrahub/proposed_change/tasks.py @@ -44,7 +44,7 @@ from infrahub.core.diff.model.path import NodeDiffFieldSummary from infrahub.core.integrity.object_conflict.conflict_recorder import ObjectConflictValidatorRecorder from infrahub.core.manager import NodeManager -from infrahub.core.protocols import CoreDataCheck, CoreValidator +from infrahub.core.protocols import CoreArtifactDefinition, CoreDataCheck, CoreValidator from infrahub.core.protocols import CoreProposedChange as InternalCoreProposedChange from infrahub.core.timestamp import Timestamp from infrahub.core.validators.checks_runner import run_checks_and_update_validator @@ -58,6 +58,7 @@ from infrahub.git.base import extract_repo_file_information from infrahub.git.models import TriggerRepositoryInternalChecks, TriggerRepositoryUserChecks from infrahub.git.repository import InfrahubRepository, get_initialized_repo +from infrahub.git.utils import fetch_artifact_definition_targets, fetch_proposed_change_generator_definition_targets from infrahub.log import get_logger from infrahub.message_bus.types import ( ProposedChangeArtifactDefinition, @@ -612,7 +613,7 @@ async def validate_artifacts_generation(model: RequestArtifactDefinitionCheck, c client = get_client() artifact_definition = await client.get( - kind=InfrahubKind.ARTIFACTDEFINITION, + kind=CoreArtifactDefinition, id=model.artifact_definition.definition_id, branch=model.source_branch, ) @@ -652,9 +653,9 @@ async def validate_artifacts_generation(model: RequestArtifactDefinitionCheck, c branch=model.source_branch, ) - await artifact_definition.targets.fetch() - group = artifact_definition.targets.peer - await group.members.fetch() + group = await fetch_artifact_definition_targets( + client=client, branch=model.source_branch, definition=artifact_definition + ) artifacts_by_member = {} for artifact in existing_artifacts: @@ -691,6 +692,7 @@ async def validate_artifacts_generation(model: RequestArtifactDefinitionCheck, c repository_kind=repository.kind, branch_name=model.source_branch, query=model.artifact_definition.query_name, + query_id=model.artifact_definition.query_id, variables=await member.extract(params=artifact_definition.parameters.value), target_id=member.id, target_kind=member.get_kind(), @@ -917,14 +919,9 @@ async def request_generator_definition_check(model: RequestGeneratorDefinitionCh branch=model.source_branch, ) - group = await client.get( - kind=InfrahubKind.GENERICGROUP, - prefetch_relationships=True, - populate_store=True, - id=model.generator_definition.group_id, - branch=model.source_branch, + group = await fetch_proposed_change_generator_definition_targets( + client=client, branch=model.source_branch, definition=model.generator_definition ) - await group.members.fetch() instance_by_member = {} for instance in existing_instances: @@ -1245,6 +1242,7 @@ async def refresh_artifacts(model: RequestProposedChangeRefreshArtifacts, contex } query { node { + id models { value } @@ -1466,6 +1464,7 @@ def _parse_artifact_definitions(definitions: list[dict]) -> list[ProposedChangeA content_type=definition["node"]["content_type"]["value"], timeout=definition["node"]["transformation"]["node"]["timeout"]["value"], query_name=definition["node"]["transformation"]["node"]["query"]["node"]["name"]["value"], + query_id=definition["node"]["transformation"]["node"]["query"]["node"]["id"], query_models=definition["node"]["transformation"]["node"]["query"]["node"]["models"]["value"] or [], repository_id=definition["node"]["transformation"]["node"]["repository"]["node"]["id"], transform_kind=definition["node"]["transformation"]["node"]["__typename"], diff --git a/backend/infrahub/workers/dependencies.py b/backend/infrahub/workers/dependencies.py index b5fce2a18c..de1db89605 100644 --- a/backend/infrahub/workers/dependencies.py +++ b/backend/infrahub/workers/dependencies.py @@ -7,6 +7,7 @@ from infrahub import config from infrahub.components import ComponentType from infrahub.constants.environment import INSTALLATION_TYPE +from infrahub.core.registry import registry from infrahub.database import InfrahubDatabase, get_db from infrahub.services.adapters.cache import InfrahubCache from infrahub.services.adapters.event import InfrahubEventService @@ -34,7 +35,13 @@ def get_component_type() -> ComponentType: def build_client() -> InfrahubClient: - return InfrahubClient(config=Config(address=config.SETTINGS.main.internal_address, retry_on_failure=True)) + client = InfrahubClient(config=Config(address=config.SETTINGS.main.internal_address, retry_on_failure=True)) + # Populate client schema cache using our internal schema cache + if registry.schema: + for branch in registry.schema.get_branches(): + client.schema.set_cache(schema=registry.schema.get_sdk_schema_branch(name=branch), branch=branch) + + return client @inject diff --git a/changelog/+785ca0cd.fixed.md b/changelog/+785ca0cd.fixed.md new file mode 100644 index 0000000000..58eab80a5e --- /dev/null +++ b/changelog/+785ca0cd.fixed.md @@ -0,0 +1 @@ +Improved artifacts generation and proposed change checks performance by leveraging caching and avoiding excessive GraphQL queries. diff --git a/poetry.lock b/poetry.lock index db8630f977..b0a0b8a1cc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -423,6 +423,21 @@ files = [ {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] +[[package]] +name = "cachetools-async" +version = "0.0.5" +description = "Provides decorators that are inspired by and work closely with cachetools' for caching asyncio functions and methods." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "cachetools_async-0.0.5-py3-none-any.whl", hash = "sha256:8f690c8bc291718a652395832e90afa797195c838e98d6a9e530dc1de66fbc34"}, + {file = "cachetools_async-0.0.5.tar.gz", hash = "sha256:e99ec8f3a78e82b4f524e78f01d15b8746531481d862ac4f2d8af2a686e1c571"}, +] + +[package.dependencies] +cachetools = ">=5" + [[package]] name = "certifi" version = "2025.8.3" @@ -666,7 +681,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} +markers = {dev = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "contourpy" @@ -5233,7 +5248,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -5242,7 +5256,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -5251,7 +5264,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -5260,7 +5272,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -5269,7 +5280,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -5708,6 +5718,18 @@ rich = ">=10.11.0" shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" +[[package]] +name = "types-cachetools" +version = "6.2.0.20250827" +description = "Typing stubs for cachetools" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_cachetools-6.2.0.20250827-py3-none-any.whl", hash = "sha256:96ae5abcb5ea1e1f1faf811a2ff8b2ce7e6d820fc42c4fcb4b332b2da485de16"}, + {file = "types_cachetools-6.2.0.20250827.tar.gz", hash = "sha256:f27febfd1b5e517e3cb1ca6daf38ad6ddb4eeb1e29bdbd81a082971ba30c0d8e"}, +] + [[package]] name = "types-python-slugify" version = "8.0.2.20240310" @@ -6563,4 +6585,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10, < 3.13" -content-hash = "1be2ae27152fc75097e8e13758ab91e6c1b3707c39035d8c5efe6e174c212ec5" +content-hash = "f8fcb7846bce7d3492e0282288671b6a87caeee156c2dd4119a50d73bcc0115b" diff --git a/pyproject.toml b/pyproject.toml index 25d14aa0cf..a0454e1b02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ netaddr = "1.3.0" authlib = "1.6.5" aiodataloader = "0.4.0" fast-depends = "^2.4.12" +cachetools-async = "^0.0.5" # Dependencies specific to the SDK rich = "^13" @@ -90,6 +91,7 @@ pre-commit = "^2.20.0" types-toml = "*" types-ujson = "*" types-pyyaml = "*" +types-cachetools = "^6.2.0.20250827" ruff = "0.11.9" invoke = "2.2.0" pytest-benchmark = "^4.0.0"