From d447e9be34f3a4b74276ab554070251bd83bdfce Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:16:36 +0200 Subject: [PATCH 01/12] fix type hint --- ayon_api/_api_helpers/attributes.py | 3 +-- ayon_api/_api_helpers/base.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ayon_api/_api_helpers/attributes.py b/ayon_api/_api_helpers/attributes.py index 26db47484..f574322cb 100644 --- a/ayon_api/_api_helpers/attributes.py +++ b/ayon_api/_api_helpers/attributes.py @@ -9,7 +9,6 @@ if typing.TYPE_CHECKING: from ayon_api.typing import ( AttributeSchemaDataDict, - AttributeSchemaDict, AttributesSchemaDict, AttributeScope, ) @@ -92,7 +91,7 @@ def remove_attribute_config(self, attribute_name: str) -> None: def get_attributes_for_type( self, entity_type: AttributeScope - ) -> dict[str, AttributeSchemaDict]: + ) -> dict[str, AttributeSchemaDataDict]: """Get attribute schemas available for an entity type. Example:: diff --git a/ayon_api/_api_helpers/base.py b/ayon_api/_api_helpers/base.py index cca8aa8e3..d9284207b 100644 --- a/ayon_api/_api_helpers/base.py +++ b/ayon_api/_api_helpers/base.py @@ -15,6 +15,7 @@ ProjectDict, StreamType, AttributeScope, + AttributeSchemaDataDict, ) _PLACEHOLDER = object() @@ -134,7 +135,7 @@ def get_user( def get_attributes_for_type( self, entity_type: AttributeScope - ) -> set[str]: + ) -> dict[str, AttributeSchemaDataDict]: raise NotImplementedError() def get_attributes_fields_for_type( From fd470385ac7b5320e6200500931e2f98394d997e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:17:03 +0200 Subject: [PATCH 02/12] use allAttrib in project --- ayon_api/_api_helpers/projects.py | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index 70099d29b..9aeeb5f65 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -663,7 +663,7 @@ def _get_project_graphql_fields( if fields is None: return set(), ProjectFetchType.REST - rest_list_fields = { + rest_fields = { "name", "code", "active", @@ -671,10 +671,11 @@ def _get_project_graphql_fields( "updatedAt", } graphql_fields = set() - if len(fields - rest_list_fields) == 0: + if len(fields - rest_fields) == 0: return graphql_fields, ProjectFetchType.RESTList must_use_graphql = False + add_all_attrib = False for field in tuple(fields): # Product types are available only in GraphQl if field == "usedTags": @@ -707,11 +708,9 @@ def _get_project_graphql_fields( elif field.startswith("bundle"): graphql_fields.add(field) - elif field == "attrib": - fields.discard("attrib") - graphql_fields |= self.get_attributes_fields_for_type( - "project" - ) + elif field == "attrib" or field.startswith("attrib."): + fields.discard(field) + add_all_attrib = True # NOTE 'config' in GraphQl is NOT the same as from REST api. # - At the moment of this comment there is missing 'productBaseTypes'. @@ -726,6 +725,8 @@ def _get_project_graphql_fields( remainders = fields - (inters | graphql_fields) if not remainders: graphql_fields |= inters + if add_all_attrib: + graphql_fields.add("allAttrib") return graphql_fields, ProjectFetchType.GraphQl if must_use_graphql: @@ -820,14 +821,19 @@ def _get_graphql_projects( all_attrib = json.loads(all_attrib) project["allAttrib"] = all_attrib - if own_attributes and all_attrib: - own_attrib = {} - if all_attrib: - own_attrib = copy.deepcopy(all_attrib) - attrib = project.get("attrib", {}) - for key in attrib.keys(): - own_attrib.setdefault(key, None) - project["ownAttrib"] = own_attrib + if all_attrib is not None: + project["ownAttrib"] = list(all_attrib) + + attrib = copy.deepcopy(all_attrib) + project["attrib"] = attrib + for name, attr_data in ( + self.get_attributes_for_type("project").items() + ): + # NOTE 'default' can be 'None' + attrib.setdefault(name, attr_data["default"]) + + if own_attributes: + fill_own_attribs(project) self._fill_project_entity_data(project) yield project From f51075f264e5833e63dc5385559a0f12c7b31f0a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:31:35 +0200 Subject: [PATCH 03/12] use allAttrib instead of attrib --- ayon_api/_api_helpers/projects.py | 12 ++++++------ ayon_api/server_api.py | 30 +++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index 9aeeb5f65..18350b7f7 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -816,15 +816,15 @@ def _get_graphql_projects( if library is not None and library is not project["library"]: continue + attrib = None all_attrib = project.get("allAttrib") if isinstance(all_attrib, str): - all_attrib = json.loads(all_attrib) - project["allAttrib"] = all_attrib + attrib = json.loads(all_attrib) - if all_attrib is not None: - project["ownAttrib"] = list(all_attrib) - - attrib = copy.deepcopy(all_attrib) + if attrib is not None: + # NOTE 'ownAttrib' logic might change in the future if + # allAttrib would return all attribute values. + project["ownAttrib"] = list(attrib) project["attrib"] = attrib for name, attr_data in ( self.get_attributes_for_type("project").items() diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 1b4668218..25c40203c 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -2424,16 +2424,21 @@ def _prepare_fields( if not fields: return - if "attrib" in fields: - fields.remove("attrib") - fields |= self.get_attributes_fields_for_type(entity_type) + add_all_attrib = False + for field in tuple(fields): + if field == "attrib" or field.startswith("attrib."): + fields.discard(field) + add_all_attrib = True if own_attributes: if entity_type == "project": - fields.add("allAttrib") + add_all_attrib = True elif entity_type in {"folder", "task"}: fields.add("ownAttrib") + if add_all_attrib: + fields.add("allAttrib") + if entity_type != "project": return @@ -2499,11 +2504,18 @@ def _prepare_advanced_filters( return filters def _convert_entity_data(self, entity: AnyEntityDict): - if not entity or "data" not in entity: + if not entity: return - entity_data = entity["data"] or {} - if isinstance(entity_data, str): - entity_data = json.loads(entity_data) + if "data" in entity: + entity_data = entity["data"] or {} + if isinstance(entity_data, str): + entity_data = json.loads(entity_data) + + entity["data"] = entity_data - entity["data"] = entity_data + all_attrib = entity.get("allAttrib") + if isinstance(all_attrib, str): + # NOTE: This expects server returns all attributes available for + # the entity type. + entity["attrib"] = json.loads(all_attrib) From 2dcce7b35279699b11e4e813a8db35954f5ee64e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:02:59 +0200 Subject: [PATCH 04/12] use similar approach using defaults in lists too --- ayon_api/_api_helpers/lists.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ayon_api/_api_helpers/lists.py b/ayon_api/_api_helpers/lists.py index df22e3844..d97fb1d5f 100644 --- a/ayon_api/_api_helpers/lists.py +++ b/ayon_api/_api_helpers/lists.py @@ -51,16 +51,17 @@ def get_entity_lists( if fields is None: fields = self.get_default_fields_for_type("entityList") - # List does not have 'attrib' field but has 'allAttrib' field - # which is json string and contains only values that are set o_fields = tuple(fields) fields = set() requires_attrib = False for field in o_fields: if field == "attrib" or field.startswith("attrib."): requires_attrib = True - field = "allAttrib" - fields.add(field) + else: + fields.add(field) + + if requires_attrib: + fields.add("allAttrib") if "items" in fields: fields.discard("items") @@ -71,7 +72,7 @@ def get_entity_lists( "items.position", } - available_attribs = [] + available_attribs = {} if requires_attrib: available_attribs = self.get_attributes_for_type("list") @@ -98,13 +99,11 @@ def get_entity_lists( entity_list["attributes"] = json.loads(attributes) if requires_attrib: - all_attrib = json.loads( - entity_list.get("allAttrib") or "{}" - ) - entity_list["attrib"] = { - attrib_name: all_attrib.get(attrib_name) - for attrib_name in available_attribs - } + attrib = json.loads(entity_list["allAttrib"]) + for attrib_name, attrib_data in available_attribs.items(): + attrib.setdefault(attrib_name, attrib_data["default"]) + + entity_list["attrib"] = attrib self._convert_entity_data(entity_list) From f58ba84269fee6e5f47da510f699fd23a09d1f7c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:15:47 +0200 Subject: [PATCH 05/12] add 'allAttrib' earlier --- ayon_api/_api_helpers/projects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index 18350b7f7..7430f28f1 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -712,6 +712,9 @@ def _get_project_graphql_fields( fields.discard(field) add_all_attrib = True + if add_all_attrib: + graphql_fields.add("allAttrib") + # NOTE 'config' in GraphQl is NOT the same as from REST api. # - At the moment of this comment there is missing 'productBaseTypes'. inters = fields & { @@ -725,8 +728,6 @@ def _get_project_graphql_fields( remainders = fields - (inters | graphql_fields) if not remainders: graphql_fields |= inters - if add_all_attrib: - graphql_fields.add("allAttrib") return graphql_fields, ProjectFetchType.GraphQl if must_use_graphql: From cc2e099e2bae577fc65d3a374bcc2e4241664f74 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:57:06 +0200 Subject: [PATCH 06/12] mark 'get_attributes_fields_for_type' as deprecated --- ayon_api/_api_helpers/attributes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ayon_api/_api_helpers/attributes.py b/ayon_api/_api_helpers/attributes.py index f574322cb..c9682e4d8 100644 --- a/ayon_api/_api_helpers/attributes.py +++ b/ayon_api/_api_helpers/attributes.py @@ -147,10 +147,18 @@ def get_attributes_fields_for_type( ) -> set[str]: """Prepare attribute fields for entity type. + DEPRECATED: Field 'attrib' is marked as deprecated and should not be + used for GraphQL queries. + Returns: set[str]: Attributes fields for entity type. """ + self.log.warning( + "Method 'get_attributes_fields_for_type' is deprecated and should" + " not be used for GraphQL queries. Use 'allAttrib' field instead" + " of 'attrib'." + ) attributes = self.get_attributes_for_type(entity_type) return { f"attrib.{attr}" From 1f1c2527b397d97aa712f3dc96062429c2720787 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:57:43 +0200 Subject: [PATCH 07/12] fix few issues with attributes --- ayon_api/_api_helpers/lists.py | 18 ++++----- ayon_api/_api_helpers/projects.py | 14 ++++--- ayon_api/constants.py | 4 -- ayon_api/server_api.py | 67 ++++++++++++++++++------------- 4 files changed, 57 insertions(+), 46 deletions(-) diff --git a/ayon_api/_api_helpers/lists.py b/ayon_api/_api_helpers/lists.py index d97fb1d5f..6bab56f91 100644 --- a/ayon_api/_api_helpers/lists.py +++ b/ayon_api/_api_helpers/lists.py @@ -53,14 +53,14 @@ def get_entity_lists( o_fields = tuple(fields) fields = set() - requires_attrib = False + add_all_attrib = False for field in o_fields: if field == "attrib" or field.startswith("attrib."): - requires_attrib = True + add_all_attrib = True else: fields.add(field) - if requires_attrib: + if add_all_attrib: fields.add("allAttrib") if "items" in fields: @@ -73,7 +73,7 @@ def get_entity_lists( } available_attribs = {} - if requires_attrib: + if "allAttrib" in fields: available_attribs = self.get_attributes_for_type("list") if active is not None: @@ -98,15 +98,13 @@ def get_entity_lists( if isinstance(attributes, str): entity_list["attributes"] = json.loads(attributes) - if requires_attrib: - attrib = json.loads(entity_list["allAttrib"]) + self._convert_entity_data(entity_list) + + attrib = entity_list.get("attrib") + if attrib is not None: for attrib_name, attrib_data in available_attribs.items(): attrib.setdefault(attrib_name, attrib_data["default"]) - entity_list["attrib"] = attrib - - self._convert_entity_data(entity_list) - yield entity_list def get_entity_list_rest( diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index 7430f28f1..b489c5a53 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -164,8 +164,10 @@ def get_rest_project( return None project = response.data attrib = project["attrib"] - for attr_name in self.get_attributes_for_type("project"): - attrib.setdefault(attr_name, None) + for attr_name, attr_data in ( + self.get_attributes_for_type("project").items() + ): + attrib.setdefault(attr_name, attr_data["default"]) self._fill_project_entity_data(project) return project @@ -809,6 +811,10 @@ def _get_graphql_projects( if project_name is not None: query.set_variable_value("projectName", project_name) + attributes = {} + if "allAttrib" in fields: + attributes = self.get_attributes_for_type("project") + for parsed_data in query.continuous_query(self): for project in parsed_data["projects"]: if active is not None and active is not project["active"]: @@ -827,9 +833,7 @@ def _get_graphql_projects( # allAttrib would return all attribute values. project["ownAttrib"] = list(attrib) project["attrib"] = attrib - for name, attr_data in ( - self.get_attributes_for_type("project").items() - ): + for name, attr_data in attributes.items(): # NOTE 'default' can be 'None' attrib.setdefault(name, attr_data["default"]) diff --git a/ayon_api/constants.py b/ayon_api/constants.py index e39fc175f..c122e4eef 100644 --- a/ayon_api/constants.py +++ b/ayon_api/constants.py @@ -33,9 +33,6 @@ "hasPassword", "updatedAt", "apiKeyPreview", - "attrib.avatarUrl", - "attrib.email", - "attrib.fullName", } # --- Project folder types --- @@ -104,7 +101,6 @@ "linkTypes", "statuses", "tags", - "attrib", } # --- Folders --- diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 25c40203c..23331f1f7 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -5,6 +5,7 @@ """ from __future__ import annotations +import copy import os import re import io @@ -1007,29 +1008,44 @@ def get_users( if not fields: fields = self.get_default_fields_for_type("user") + else: + fields = set(fields) + add_all_attrib = False + for field in tuple(fields): + if field == "attrib" or field.startswith("attrib."): + fields.discard(field) + add_all_attrib = True + + if add_all_attrib: + fields.add("allAttrib") - query = users_graphql_query(set(fields)) + query = users_graphql_query(fields) for attr, filter_value in filters.items(): query.set_variable_value(attr, filter_value) - attributes = self.get_attributes_for_type("user") + attributes = {} + if "allAttrib" in fields: + attributes = self.get_attributes_for_type("user") + for parsed_data in query.continuous_query(self): for user in parsed_data["users"]: access_groups = user.get("accessGroups") if isinstance(access_groups, str): user["accessGroups"] = json.loads(access_groups) - all_attrib = user.get("allAttrib") - if isinstance(all_attrib, str): - user["allAttrib"] = json.loads(all_attrib) - if "attrib" in user: - user["ownAttrib"] = user["attrib"].copy() - attrib = user["attrib"] - for key, value in tuple(attrib.items()): - if value is not None: - continue - attr_def = attributes.get(key) - if attr_def is not None: - attrib[key] = attr_def["default"] + + attrib = user.get("allAttrib") + if isinstance(attrib, str): + attrib = json.loads(attrib) + + if attrib is not None: + own_attrib = copy.deepcopy(attrib) + user["ownAttrib"] = own_attrib + for name, attr_data in attributes.items(): + attrib.setdefault(name, attr_data["default"]) + own_attrib.setdefault(name, None) + + user["attrib"] = attrib + yield user def get_user_by_name( @@ -1089,10 +1105,9 @@ def get_user( response.raise_for_status() user = response.data - # NOTE Server does return only filled attributes right now. - # This would fill all missing attributes with 'None'. - # for attr_name in self.get_attributes_for_type("user"): - # user["attrib"].setdefault(attr_name, None) + attributes = self.get_attributes_for_type("user") + for attr_name, attr_data in attributes.items(): + user["attrib"].setdefault(attr_name, attr_data["default"]) fill_own_attribs(user) return user @@ -2124,6 +2139,9 @@ def get_default_fields_for_type(self, entity_type: str) -> set[str]: if entity_type == "activity": return set(DEFAULT_ACTIVITY_FIELDS) + if entity_type == "productType": + return set(DEFAULT_PRODUCT_TYPE_FIELDS) + if entity_type == "project": entity_type_defaults = set(DEFAULT_PROJECT_FIELDS) maj_v, min_v, patch_v, _, _ = self.server_version_tuple @@ -2154,9 +2172,6 @@ def get_default_fields_for_type(self, entity_type: str) -> set[str]: if not self.graphql_allows_traits_in_representations: entity_type_defaults.discard("traits") - elif entity_type == "productType": - entity_type_defaults = set(DEFAULT_PRODUCT_TYPE_FIELDS) - elif entity_type == "workfile": entity_type_defaults = set(DEFAULT_WORKFILE_INFO_FIELDS) @@ -2165,15 +2180,13 @@ def get_default_fields_for_type(self, entity_type: str) -> set[str]: elif entity_type == "entityList": entity_type_defaults = set(DEFAULT_ENTITY_LIST_FIELDS) - # Attributes scope is 'list' - entity_type = "list" else: raise ValueError(f"Unknown entity type \"{entity_type}\"") - return ( - entity_type_defaults - | self.get_attributes_fields_for_type(entity_type) - ) + + entity_type_defaults.add("allAttrib") + + return entity_type_defaults def get_rest_entity_by_id( self, From 436614210a4a1f0a521a8b4f29155cdf65d9cb4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:02:03 +0200 Subject: [PATCH 08/12] reuse existing method --- ayon_api/server_api.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 23331f1f7..7ad2f5ba0 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1010,14 +1010,7 @@ def get_users( fields = self.get_default_fields_for_type("user") else: fields = set(fields) - add_all_attrib = False - for field in tuple(fields): - if field == "attrib" or field.startswith("attrib."): - fields.discard(field) - add_all_attrib = True - - if add_all_attrib: - fields.add("allAttrib") + self._prepare_fields("user", fields) query = users_graphql_query(fields) for attr, filter_value in filters.items(): From af880577e0f3072fca0d62353e22501e570fcb4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:42:24 +0200 Subject: [PATCH 09/12] remove unused import --- ayon_api/_api_helpers/projects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index b489c5a53..58857248e 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -1,6 +1,5 @@ from __future__ import annotations -import copy import json import platform import warnings From f3bda4190044208002481dabdb5a10f463b35972 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:42:59 +0200 Subject: [PATCH 10/12] better handling of cached attributes --- ayon_api/_api_helpers/attributes.py | 118 +++++++++++++++++++++------- 1 file changed, 90 insertions(+), 28 deletions(-) diff --git a/ayon_api/_api_helpers/attributes.py b/ayon_api/_api_helpers/attributes.py index c9682e4d8..c9deb6356 100644 --- a/ayon_api/_api_helpers/attributes.py +++ b/ayon_api/_api_helpers/attributes.py @@ -1,38 +1,107 @@ from __future__ import annotations +import copy +import time import typing from typing import Optional -import copy from .base import BaseServerAPI if typing.TYPE_CHECKING: from ayon_api.typing import ( + AttributeSchemaDict, AttributeSchemaDataDict, AttributesSchemaDict, AttributeScope, ) +class _AttributesCache: + _schema = None + _last_fetch = 0 + _timeout = 60 + _attributes_by_type = {} + + def reset_schema(self) -> None: + self._schema = None + self._last_fetch = 0 + self._attributes_by_type = {} + + def set_timeout(self, timeout: int) -> None: + self._timeout = timeout + + def get_schema(self) -> AttributesSchemaDict: + return copy.deepcopy(self._schema) + + def set_schema(self, schema: AttributesSchemaDict) -> None: + self._schema = schema + self._last_fetch = time.time() + + def is_valid(self) -> bool: + if self._schema is None: + return False + return time.time() - self._last_fetch < self._timeout + + def invalidate(self) -> None: + if not self.is_valid(): + self.reset_schema() + + def get_attributes_for_type( + self, entity_type: AttributeScope + ) -> list[AttributeSchemaDict]: + attributes = self._attributes_by_type.get(entity_type) + if attributes is not None: + return attributes + + attributes_schema = self.get_schema() + if attributes_schema is None: + raise ValueError("Attributes schema is not cached.") + + attributes = [] + for attr in attributes_schema["attributes"]: + if entity_type not in attr["scope"]: + continue + attributes.append(attr) + + self._attributes_by_type[entity_type] = attributes + return attributes + + class AttributesAPI(BaseServerAPI): - _attributes_schema = None - _entity_type_attributes_cache = {} + _attributes_cache = _AttributesCache() def get_attributes_schema( self, use_cache: bool = True ) -> AttributesSchemaDict: if not use_cache: - self.reset_attributes_schema() + self._attributes_cache.reset_schema() + else: + self._attributes_cache.invalidate() - if self._attributes_schema is None: + if not self._attributes_cache.is_valid(): result = self.get("attributes") result.raise_for_status() - self._attributes_schema = result.data - return copy.deepcopy(self._attributes_schema) + self._attributes_cache.set_schema(result.data) + return self._attributes_cache.get_schema() def reset_attributes_schema(self) -> None: - self._attributes_schema = None - self._entity_type_attributes_cache = {} + """Reset attributes schema cache. + + DEPRECATED: + Use 'reset_attributes_cache' instead. + + """ + self.log.warning( + "Used deprecated function 'reset_attributes_schema'." + " Please use 'reset_attributes_cache' instead." + ) + self.reset_attributes_cache() + + def reset_attributes_cache(self) -> None: + self._attributes_cache.reset_schema() + + def set_attributes_cache_timeout(self, timeout: int) -> None: + self._attributes_cache.set_timeout(timeout) def set_attribute_config( self, @@ -63,12 +132,10 @@ def set_attribute_config( position=position, builtin=builtin ) - if response.status_code != 204: - # TODO raise different exception - raise ValueError( - f"Attribute \"{attribute_name}\" was not created/updated." - f" {response.detail}" - ) + response.raise_for_status( + f"Attribute \"{attribute_name}\" was not created/updated." + f" {response.detail}" + ) self.reset_attributes_schema() @@ -128,19 +195,14 @@ def get_attributes_for_type( for entered entity type. """ - attributes = self._entity_type_attributes_cache.get(entity_type) - if attributes is None: - attributes_schema = self.get_attributes_schema() - attributes = {} - for attr in attributes_schema["attributes"]: - if entity_type not in attr["scope"]: - continue - attr_name = attr["name"] - attributes[attr_name] = attr["data"] - - self._entity_type_attributes_cache[entity_type] = attributes - - return copy.deepcopy(attributes) + # Make sure attributes are cached + self.get_attributes_schema() + return { + attr["name"]: attr["data"] + for attr in self._attributes_cache.get_attributes_for_type( + entity_type + ) + } def get_attributes_fields_for_type( self, entity_type: AttributeScope From d9649c564132a6b768c50a67b09dd83faaea0d1d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:44:07 +0200 Subject: [PATCH 11/12] update public api --- ayon_api/__init__.py | 4 ++++ ayon_api/_api.py | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index fc89a1dfb..c964c24ef 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -148,6 +148,8 @@ enroll_event_job, get_attributes_schema, reset_attributes_schema, + reset_attributes_cache, + set_attributes_cache_timeout, set_attribute_config, remove_attribute_config, get_attributes_for_type, @@ -433,6 +435,8 @@ "enroll_event_job", "get_attributes_schema", "reset_attributes_schema", + "reset_attributes_cache", + "set_attributes_cache_timeout", "set_attribute_config", "remove_attribute_config", "get_attributes_for_type", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index d18d00e74..fb421c12e 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -3582,10 +3582,30 @@ def get_attributes_schema( def reset_attributes_schema() -> None: + """Reset attributes schema cache. + + DEPRECATED: + Use 'reset_attributes_cache' instead. + + """ con = get_server_api_connection() return con.reset_attributes_schema() +def reset_attributes_cache() -> None: + con = get_server_api_connection() + return con.reset_attributes_cache() + + +def set_attributes_cache_timeout( + timeout: int, +) -> None: + con = get_server_api_connection() + return con.set_attributes_cache_timeout( + timeout=timeout, + ) + + def set_attribute_config( attribute_name: str, data: AttributeSchemaDataDict, @@ -3622,7 +3642,7 @@ def remove_attribute_config( def get_attributes_for_type( entity_type: AttributeScope, -) -> dict[str, AttributeSchemaDict]: +) -> dict[str, AttributeSchemaDataDict]: """Get attribute schemas available for an entity type. Example:: @@ -3670,6 +3690,9 @@ def get_attributes_fields_for_type( ) -> set[str]: """Prepare attribute fields for entity type. + DEPRECATED: Field 'attrib' is marked as deprecated and should not be + used for GraphQL queries. + Returns: set[str]: Attributes fields for entity type. From 5bc86186f48408330ab74b0eae9707abc824ba7f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:59:49 +0200 Subject: [PATCH 12/12] remove unused import --- ayon_api/_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index fb421c12e..87437368f 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -55,7 +55,6 @@ EnrollEventData, AttributeScope, AttributeSchemaDataDict, - AttributeSchemaDict, AttributesSchemaDict, AddonsInfoDict, InstallersInfoDict,