From b8e1050106d405d8e09844825bfdbf5283c89647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 30 May 2025 14:37:13 +0200 Subject: [PATCH 1/7] :sparkles: add support for product base types --- ayon_api/__init__.py | 3 + ayon_api/_api.py | 84 +++++++++++++++++- ayon_api/constants.py | 7 ++ ayon_api/entity_hub.py | 30 ++++++- ayon_api/graphql_queries.py | 48 +++++++++++ ayon_api/operations.py | 115 ++++++++++++++----------- ayon_api/server_api.py | 167 ++++++++++++++++++++++++++++++------ ayon_api/typing.py | 5 ++ 8 files changed, 374 insertions(+), 85 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 373cbad88..6c70e855f 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -181,6 +181,9 @@ get_product_types, get_project_product_types, get_product_type_names, + get_product_base_types, + get_project_product_base_types, + get_product_base_type_names, create_product, update_product, delete_product, diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 9c06b6f45..d6b47351f 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -65,6 +65,7 @@ FlatFolderDict, ProjectHierarchyDict, ProductTypeDict, + ProductBaseTypeDict, StreamType, ) @@ -4328,6 +4329,7 @@ def get_products( product_names: Optional[Iterable[str]] = None, folder_ids: Optional[Iterable[str]] = None, product_types: Optional[Iterable[str]] = None, + product_base_types: Optional[Iterable[str]] = None, product_name_regex: Optional[str] = None, product_path_regex: Optional[str] = None, names_by_folder_ids: Optional[Dict[str, Iterable[str]]] = None, @@ -4337,21 +4339,22 @@ def get_products( fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, ) -> Generator["ProductDict", None, None]: - """Query products from server. + """Query products from the server. Todos: Separate 'name_by_folder_ids' filtering to separated method. It cannot be combined with some other filters. Args: - project_name (str): Name of project. + project_name (str): Name of the project. product_ids (Optional[Iterable[str]]): Task ids to filter. product_names (Optional[Iterable[str]]): Task names used for filtering. folder_ids (Optional[Iterable[str]]): Ids of task parents. - Use 'None' if folder is direct child of project. + Use 'None' if folder is direct child of the project. product_types (Optional[Iterable[str]]): Product types used for filtering. + product_base_types (Optional[Iterable[str]]): Product base types product_name_regex (Optional[str]): Filter products by name regex. product_path_regex (Optional[str]): Filter products by path regex. Path starts with folder path and ends with product name. @@ -4380,6 +4383,7 @@ def get_products( product_names=product_names, folder_ids=folder_ids, product_types=product_types, + product_base_types=product_base_types, product_name_regex=product_name_regex, product_path_regex=product_path_regex, names_by_folder_ids=names_by_folder_ids, @@ -4461,7 +4465,7 @@ def get_product_types( ) -> List["ProductTypeDict"]: """Types of products. - This is server wide information. Product types have 'name', 'icon' and + This is the server-wide information. Product types have 'name', 'icon' and 'color'. Args: @@ -4477,6 +4481,27 @@ def get_product_types( ) +def get_product_base_types( + fields: Optional[Iterable[str]] = None, +) -> List["ProductBaseTypeDict"]: + """Base types of products. + + This is the server-wide information. Product base types have 'name', 'icon' + and 'color'. + + Args: + fields (Optional[Iterable[str]]): Product base types fields to query. + + Returns: + list[ProductBaseTypeDict]: Product base types information. + + """ + con = get_server_api_connection() + return con.get_product_base_types( + fields=fields, + ) + + def get_project_product_types( project_name: str, fields: Optional[Iterable[str]] = None, @@ -4501,6 +4526,30 @@ def get_project_product_types( ) +def get_project_product_base_types( + project_name: str, + fields: Optional[Iterable[str]] = None, +) -> List["ProductBaseTypeDict"]: + """Base types of products available in a project. + + Filter only product base types available in a project. + + Args: + project_name (str): Name of project where to look for + product base types. + fields (Optional[Iterable[str]]): Product base types fields to query. + + Returns: + List[ProductBaseTypeDict]: Product base types information. + + """ + con = get_server_api_connection() + return con.get_project_product_base_types( + project_name=project_name, + fields=fields, + ) + + def get_product_type_names( project_name: Optional[str] = None, product_ids: Optional[Iterable[str]] = None, @@ -4528,6 +4577,33 @@ def get_product_type_names( ) +def get_product_base_type_names( + project_name: Optional[str] = None, + product_ids: Optional[Iterable[str]] = None, +) -> Set[str]: + """Base product type names. + + Warnings: + Similar use case as `get_product_type_names` but for base + product types. + + Args: + project_name (Optional[str]): Name of project where to look for + queried entities. + product_ids (Optional[Iterable[str]]): Product ids filter. Can be + used only with 'project_name'. + + Returns: + set[str]: Base product type names. + + """ + con = get_server_api_connection() + return con.get_product_base_type_names( + project_name=project_name, + product_ids=product_ids, + ) + + def create_product( project_name: str, name: str, diff --git a/ayon_api/constants.py b/ayon_api/constants.py index 93ff2877f..312d3eb99 100644 --- a/ayon_api/constants.py +++ b/ayon_api/constants.py @@ -48,6 +48,13 @@ "color", } +# --- Product base type --- +DEFAULT_PRODUCT_BASE_TYPE_FIELDS = { + "name", + "icon", + "color", +} + # --- Project --- DEFAULT_PROJECT_FIELDS = { "active", diff --git a/ayon_api/entity_hub.py b/ayon_api/entity_hub.py index ead6b4078..7fa01e418 100644 --- a/ayon_api/entity_hub.py +++ b/ayon_api/entity_hub.py @@ -435,6 +435,7 @@ def add_new_product( self, name: str, product_type: str, + product_base_type: Optional[str] = None, folder_id: Optional["Union[str, _CustomNone]"] = UNKNOWN_VALUE, tags: Optional[Iterable[str]] = None, attribs: Optional[Dict[str, Any]] = UNKNOWN_VALUE, @@ -443,10 +444,11 @@ def add_new_product( entity_id: Optional[str] = None, created: Optional[bool] = True, ): - """Create task object and add it to entity hub. + """Create a task object and add it to the entity hub. Args: name (str): Name of entity. + product_base_type (str): Base type of product. product_type (str): Type of product. folder_id (Union[str, None]): Parent folder id. tags (Optional[Iterable[str]]): Folder tags. @@ -458,6 +460,11 @@ def add_new_product( created (Optional[bool]): Entity is new. When 'None' is passed the value is defined based on value of 'entity_id'. + Todo: + - Once the product base type is implemented and established, + it should be made mandatory to pass it and product_type + itself should be optional. + Returns: ProductEntity: Added product entity. @@ -465,6 +472,7 @@ def add_new_product( product_entity = ProductEntity( name=name, product_type=product_type, + product_base_type=product_base_type, folder_id=folder_id, tags=tags, attribs=attribs, @@ -3406,6 +3414,7 @@ def to_create_body_data(self): class ProductEntity(BaseEntity): _supports_name = True _supports_tags = True + _supports_base_type = True entity_type = "product" parent_entity_types = ["folder"] @@ -3414,6 +3423,7 @@ def __init__( self, name: str, product_type: str, + product_base_type: Optional[str] = None, folder_id: Optional["Union[str, _CustomNone]"] = UNKNOWN_VALUE, tags: Optional[Iterable[str]] = None, attribs: Optional[Dict[str, Any]] = UNKNOWN_VALUE, @@ -3435,6 +3445,7 @@ def __init__( entity_hub=entity_hub, ) self._product_type = product_type + self._product_base_type = product_base_type self._orig_product_type = product_type @@ -3454,6 +3465,21 @@ def set_product_type(self, product_type): product_type = property(get_product_type, set_product_type) + def get_product_base_type(self) -> Optional[str]: + """Get the product base type. + + Returns: + Optional[str]: The product base type, or None if not set. + + """ + return self._product_base_type + + def set_product_base_type(self, product_base_type: str) -> None: + """Set the product base type.""" + self._product_base_type = product_base_type + + product_base_type = property(get_product_base_type, set_product_base_type) + def lock(self): super().lock() self._orig_product_type = self._product_type @@ -3475,6 +3501,7 @@ def from_entity_data(cls, product, entity_hub): return cls( name=product["name"], product_type=product["productType"], + product_base_type=product["productBaseType"], folder_id=product["folderId"], tags=product["tags"], attribs=product["attrib"], @@ -3492,6 +3519,7 @@ def to_create_body_data(self): output = { "name": self.name, "productType": self.product_type, + "productBaseType": self.product_base_type, "folderId": self.parent_id, } diff --git a/ayon_api/graphql_queries.py b/ayon_api/graphql_queries.py index 18b76f059..75938a7e9 100644 --- a/ayon_api/graphql_queries.py +++ b/ayon_api/graphql_queries.py @@ -119,6 +119,28 @@ def product_types_query(fields): return query +def product_base_types_query(fields): + query = GraphQlQuery("ProductBaseTypes") + product_base_types_field = query.add_field("productBaseTypes") + + nested_fields = fields_to_dict(fields) + + query_queue = collections.deque() + for key, value in nested_fields.items(): + query_queue.append((key, value, product_base_types_field)) + + while query_queue: + item = query_queue.popleft() + key, value, parent = item + field = parent.add_field(key) + if value is FIELD_VALUE: + continue + + for k, v in value.items(): + query_queue.append((k, v, field)) + return query + + def project_product_types_query(fields): query = GraphQlQuery("ProjectProductTypes") project_query = query.add_field("project") @@ -143,6 +165,30 @@ def project_product_types_query(fields): return query +def project_product_base_types_query(fields): + query = GraphQlQuery("ProjectProductBaseTypes") + project_query = query.add_field("project") + project_name_var = query.add_variable("projectName", "String!") + project_query.set_filter("name", project_name_var) + product_base_types_field = project_query.add_field("productBaseTypes") + nested_fields = fields_to_dict(fields) + + query_queue = collections.deque() + for key, value in nested_fields.items(): + query_queue.append((key, value, product_base_types_field)) + + while query_queue: + item = query_queue.popleft() + key, value, parent = item + field = parent.add_field(key) + if value is FIELD_VALUE: + continue + + for k, v in value.items(): + query_queue.append((k, v, field)) + return query + + def folders_graphql_query(fields): query = GraphQlQuery("FoldersQuery") project_name_var = query.add_variable("projectName", "String!") @@ -298,6 +344,7 @@ def products_graphql_query(fields): product_names_var = query.add_variable("productNames", "[String!]") folder_ids_var = query.add_variable("folderIds", "[String!]") product_types_var = query.add_variable("productTypes", "[String!]") + product_base_types_var = query.add_variable("productBaseTypes", "[String!]") product_name_regex_var = query.add_variable("productNameRegex", "String!") product_path_regex_var = query.add_variable("productPathRegex", "String!") statuses_var = query.add_variable("productStatuses.", "[String!]") @@ -311,6 +358,7 @@ def products_graphql_query(fields): products_field.set_filter("names", product_names_var) products_field.set_filter("folderIds", folder_ids_var) products_field.set_filter("productTypes", product_types_var) + products_field.set_filter("productBaseTypes", product_base_types_var) products_field.set_filter("statuses", statuses_var) products_field.set_filter("tags", tags_var) products_field.set_filter("nameEx", product_name_regex_var) diff --git a/ayon_api/operations.py b/ayon_api/operations.py index d4383fda1..bf977dace 100644 --- a/ayon_api/operations.py +++ b/ayon_api/operations.py @@ -1,8 +1,10 @@ +from __future__ import annotations import os import copy import collections import uuid from abc import ABC, abstractmethod +from typing import Any, Iterable, Optional from ._api import get_server_api_connection from .utils import create_entity_id, REMOVED_VALUE, NOT_SET @@ -111,26 +113,28 @@ def new_folder_entity( def new_product_entity( - name, - product_type, - folder_id, - status=None, - tags=None, - attribs=None, - data=None, - entity_id=None -): - """Create skeleton data of product entity. + name: str, + produc_base_type: str, + product_type: str, + folder_id: str, + status: Optional[str] = None, + tags: Optional[list[str]] = None, + attribs: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + entity_id: Optional[str] = None +) -> dict[str, Any]: + """Create skeleton data of the product entity. Args: - name (str): Is considered as unique identifier of - product under folder. + name (str): Is considered as a unique identifier of + the product under the folder. + product_base_type (str): Base type of the product, e.g. "render", product_type (str): Product type. folder_id (str): Parent folder id. status (Optional[str]): Product status. tags (Optional[List[str]]): List of tags. attribs (Optional[Dict[str, Any]]): Explicitly set attributes - of product. + of the product. data (Optional[Dict[str, Any]]): product entity data. Empty dictionary is used if not passed. entity_id (Optional[str]): Predefined id of entity. New id is @@ -1090,22 +1094,24 @@ def delete_task(self, project_name, task_id): def create_product( self, - project_name, - name, - product_type, - folder_id, - attrib=None, - data=None, - tags=None, - status=None, - active=None, - product_id=None, - ): - """Create new product. + project_name: str, + name: str, + product_base_type: str, + product_type: str, + folder_id: str, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + product_id: Optional[str] = None, + ) -> CreateOperation: + """Create a new product. Args: project_name (str): Project name. name (str): Product name. + product_base_type (str): Base type of the product, e.g. "render", product_type (str): Product type. folder_id (str): Parent folder id. attrib (Optional[dict[str, Any]]): Product attributes. @@ -1125,6 +1131,7 @@ def create_product( create_data = { "id": product_id, "name": name, + "productBaseType": product_base_type, "productType": product_type, "folderId": folder_id, } @@ -1144,20 +1151,22 @@ def create_product( def update_product( self, - project_name, - product_id, - name=None, - folder_id=None, - product_type=None, - attrib=None, - data=None, - tags=None, - status=None, - active=None, - ): + project_name: str, + product_id: str, + name: Optional[str] = None, + folder_id: Optional[str] = None, + product_base_type: Optional[str] = None, + product_type: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + ) -> UpdateOperation: """Update product entity on server. - Update of ``data`` will override existing value on folder entity. + Update of ``data`` will override the existing value on + the folder entity. Update of ``attrib`` does change only passed attributes. If you want to unset value, use ``None``. @@ -1167,6 +1176,7 @@ def update_product( product_id (str): Product id. name (Optional[str]): New product name. folder_id (Optional[str]): New product id. + product_base_type (Optional[str]): New product base type. product_type (Optional[str]): New product type. attrib (Optional[dict[str, Any]]): New product attributes. data (Optional[dict[str, Any]]): New product data. @@ -1178,20 +1188,21 @@ def update_product( UpdateOperation: Object of update operation. """ - update_data = {} - for key, value in ( - ("name", name), - ("productType", product_type), - ("folderId", folder_id), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - update_data[key] = value - + update_data = { + key: value + for key, value in ( + ("name", name), + ("productBaseType", product_base_type), + ("productType", product_type), + ("folderId", folder_id), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ) + if value is not None + } return self.update_entity( project_name, "product", @@ -1200,7 +1211,7 @@ def update_product( ) def delete_product(self, project_name, product_id): - """Delete product. + """Delete the product. Args: project_name (str): Project name. diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 0409e9310..0d2714725 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -40,6 +40,7 @@ SERVER_RETRIES_ENV_KEY, DEFAULT_FOLDER_TYPE_FIELDS, DEFAULT_TASK_TYPE_FIELDS, + DEFAULT_PRODUCT_BASE_TYPE_FIELDS, DEFAULT_PRODUCT_TYPE_FIELDS, DEFAULT_PROJECT_FIELDS, DEFAULT_FOLDER_FIELDS, @@ -57,6 +58,7 @@ from .graphql_queries import ( project_graphql_query, projects_graphql_query, + project_product_base_types_query, project_product_types_query, product_types_query, folders_graphql_query, @@ -130,6 +132,7 @@ ProjectHierarchyDict, ProductTypeDict, + ProductBaseTypeDict, StreamType, ) @@ -2760,6 +2763,9 @@ def get_default_fields_for_type(self, entity_type: str) -> Set[str]: elif entity_type == "taskType": entity_type_defaults = set(DEFAULT_TASK_TYPE_FIELDS) + elif entity_type == "productBaseType": + entity_type_defaults = set(DEFAULT_PRODUCT_BASE_TYPE_FIELDS) + elif entity_type == "productType": entity_type_defaults = set(DEFAULT_PRODUCT_TYPE_FIELDS) @@ -5748,6 +5754,7 @@ def get_products( product_ids: Optional[Iterable[str]] = None, product_names: Optional[Iterable[str]]=None, folder_ids: Optional[Iterable[str]]=None, + product_base_types: Optional[Iterable[str]]=None, product_types: Optional[Iterable[str]]=None, product_name_regex: Optional[str] = None, product_path_regex: Optional[str] = None, @@ -5760,9 +5767,9 @@ def get_products( ) -> Generator["ProductDict", None, None]: """Query products from server. - Todos: - Separate 'name_by_folder_ids' filtering to separated method. It - cannot be combined with some other filters. + Todo: + - Separate 'name_by_folder_ids' filtering to separated method. It + cannot be combined with some other filters. Args: project_name (str): Name of project. @@ -5771,11 +5778,14 @@ def get_products( filtering. folder_ids (Optional[Iterable[str]]): Ids of task parents. Use 'None' if folder is direct child of project. + product_base_types (Optional[Iterable[str]]): Product base types + filtering. product_types (Optional[Iterable[str]]): Product types used for filtering. product_name_regex (Optional[str]): Filter products by name regex. product_path_regex (Optional[str]): Filter products by path regex. - Path starts with folder path and ends with product name. + Path starts with the folder path and ends with + the product name. names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product name filtering by folder id. statuses (Optional[Iterable[str]]): Product statuses used @@ -5785,7 +5795,7 @@ def get_products( active (Optional[bool]): Filter active/inactive products. Both are returned if is set to None. fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned + the folder. All possible folder fields are returned if 'None' is passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for products. @@ -5863,6 +5873,7 @@ def get_products( if not _prepare_list_filters( filters, ("productIds", product_ids), + ("productBaseTypes", product_base_types), ("productTypes", product_types), ("productStatuses", statuses), ("productTags", tags), @@ -5947,7 +5958,7 @@ def get_product_by_name( product_name: str, folder_id: str, fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER + own_attributes=_PLACEHOLDER, ) -> Optional["ProductDict"]: """Query product entity by name and folder id. @@ -5972,7 +5983,7 @@ def get_product_by_name( folder_ids=[folder_id], active=None, fields=fields, - own_attributes=own_attributes + own_attributes=own_attributes, ) for product in products: return product @@ -5983,8 +5994,8 @@ def get_product_types( ) -> List["ProductTypeDict"]: """Types of products. - This is server wide information. Product types have 'name', 'icon' and - 'color'. + This is the server-wide information. Product types have + 'name', 'icon' and 'color'. Args: fields (Optional[Iterable[str]]): Product types fields to query. @@ -6005,12 +6016,12 @@ def get_product_types( def get_project_product_types( self, project_name: str, fields: Optional[Iterable[str]] = None ) -> List["ProductTypeDict"]: - """Types of products available on a project. - - Filter only product types available on project. + """Types of products available in a project. + Filter only product types available in a project. +I Args: - project_name (str): Name of project where to look for + project_name (str): Name of the project where to look for product types. fields (Optional[Iterable[str]]): Product types fields to query. @@ -6068,12 +6079,102 @@ def get_product_type_names( ) } + def get_product_base_types( + self, fields: Optional[Iterable[str]] = None + ) -> List["ProductBaseTypeDict"]: + """Types of product base types. + + Args: + fields (Optional[Iterable[str]]): Product base types fields + to query. + + Returns: + list[ProductBaseTypeDict]: Product base types information. + + """ + if not fields: + fields = self.get_default_fields_for_type("productBaseType") + + query = product_types_query(fields) + + parsed_data = query.query(self) + + return parsed_data.get("productBaseTypes", []) + + + def get_project_product_base_types( + self, + project_name: str, + fields: Optional[Iterable[str]] = None + ) -> List["ProductBaseTypeDict"]: + """Product base types available in a project. + + Filter only product base types available in a project. + + Args: + project_name (str): Name of the project where to look for + product base types. + fields (Optional[Iterable[str]]): Product types fields to query. + + Returns: + List[ProductBaseTypeDict]: Product Base types information. + + """ + if not fields: + fields = self.get_default_fields_for_type("productBaseType") + + query = project_product_base_types_query(fields) + query.set_variable_value("projectName", project_name) + + parsed_data = query.query(self) + + return parsed_data.get("project", {}).get("productBaseTypes", []) + + + def get_product_base_type_names( + self, + project_name: Optional[str] = None, + product_ids: Optional[Iterable[str]] = None, + ) -> Set[str]: + """Get projects roduct base type names. + + Args: + project_name (Optional[str]): Name of project where to look for + queried entities. + product_ids (Optional[Iterable[str]]): Product ids filter. Can be + used only with 'project_name'. + + Returns: + set[str]: Product base type names used in the project. + + """ + if project_name and product_ids: + products = self.get_products( + project_name, + product_ids=product_ids, + fields=["productBaseType"], + active=None, + ) + return { + product["productBaseType"] + for product in products + } + + return { + product_info["name"] + for product_info in self.get_project_product_base_types( + project_name, fields=["name"] + ) + } + + def create_product( self, project_name: str, name: str, product_type: str, folder_id: str, + product_base_type: Optional[str] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, tags: Optional[Iterable[str]] =None, @@ -6081,13 +6182,14 @@ def create_product( active: "Union[bool, None]" = None, product_id: Optional[str] = None, ) -> str: - """Create new product. + """Create a new product. Args: project_name (str): Project name. name (str): Product name. product_type (str): Product type. folder_id (str): Parent folder id. + product_base_type (Optional[str]): Product base type. attrib (Optional[dict[str, Any]]): Product attributes. data (Optional[dict[str, Any]]): Product data. tags (Optional[Iterable[str]]): Product tags. @@ -6096,6 +6198,11 @@ def create_product( product_id (Optional[str]): Product id. If not passed new id is generated. + Todo: + - Once the product base type is implemented and established, + it should be made mandatory to pass it and product_type + itself should be optional. + Returns: str: Product id. @@ -6105,6 +6212,7 @@ def create_product( create_data = { "id": product_id, "name": name, + "productBaseType": product_base_type, "productType": product_type, "folderId": folder_id, } @@ -6131,6 +6239,7 @@ def update_product( product_id: str, name: Optional[str] = None, folder_id: Optional[str] = None, + product_base_type: Optional[str] = None, product_type: Optional[str] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, @@ -6150,6 +6259,7 @@ def update_product( product_id (str): Product id. name (Optional[str]): New product name. folder_id (Optional[str]): New product id. + product_base_type (Optional[str]): New product base type. product_type (Optional[str]): New product type. attrib (Optional[dict[str, Any]]): New product attributes. data (Optional[dict[str, Any]]): New product data. @@ -6158,20 +6268,21 @@ def update_product( active (Optional[bool]): New product active state. """ - update_data = {} - for key, value in ( - ("name", name), - ("productType", product_type), - ("folderId", folder_id), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - update_data[key] = value - + update_data = { + key: value + for key, value in ( + ("name", name), + ("productBaseType", product_base_type), + ("productType", product_type), + ("folderId", folder_id), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ) + if value is not None + } response = self.patch( f"projects/{project_name}/products/{product_id}", **update_data diff --git a/ayon_api/typing.py b/ayon_api/typing.py index 07f041aab..8e1e73364 100644 --- a/ayon_api/typing.py +++ b/ayon_api/typing.py @@ -352,4 +352,9 @@ class ProductTypeDict(TypedDict): icon: Optional[str] +class ProductBaseTypeDict(TypedDict): + name: str + color: Optional[str] + icon: Optional[str] + StreamType = Union[io.BytesIO, BinaryIO] From 1eeae553455ee956f737cf2a096838a6f6c26fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 30 May 2025 14:56:19 +0200 Subject: [PATCH 2/7] :bug: remove the stray character in comment --- ayon_api/server_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 0d2714725..70e411425 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -6019,7 +6019,7 @@ def get_project_product_types( """Types of products available in a project. Filter only product types available in a project. -I + Args: project_name (str): Name of the project where to look for product types. From 4d83ab80ae47b15df86149ae5fd2eb60bc27bb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 6 Jun 2025 13:52:56 +0200 Subject: [PATCH 3/7] :sparkles: add `productBaseType` to PRODUCT_FIELDS --- ayon_api/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ayon_api/constants.py b/ayon_api/constants.py index 312d3eb99..3c6a9f6d7 100644 --- a/ayon_api/constants.py +++ b/ayon_api/constants.py @@ -105,6 +105,7 @@ "folderId", "active", "productType", + "productBaseType", "data", "status", "tags", From ceff641fd0f59a497d0e8e7d859af6138c526fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 10 Jun 2025 13:11:31 +0200 Subject: [PATCH 4/7] :recycle: better handling of product base type in entity operations --- ayon_api/operations.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ayon_api/operations.py b/ayon_api/operations.py index bf977dace..8cdc2fbef 100644 --- a/ayon_api/operations.py +++ b/ayon_api/operations.py @@ -114,14 +114,14 @@ def new_folder_entity( def new_product_entity( name: str, - produc_base_type: str, product_type: str, folder_id: str, status: Optional[str] = None, tags: Optional[list[str]] = None, attribs: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, - entity_id: Optional[str] = None + entity_id: Optional[str] = None, + product_base_type: Optional[str] = None, ) -> dict[str, Any]: """Create skeleton data of the product entity. @@ -158,6 +158,9 @@ def new_product_entity( "data": data, "folderId": _create_or_convert_to_id(folder_id), } + if product_base_type: + output["productBaseType"] = product_base_type + if status: output["status"] = status if tags: @@ -1096,7 +1099,6 @@ def create_product( self, project_name: str, name: str, - product_base_type: str, product_type: str, folder_id: str, attrib: Optional[dict[str, Any]] = None, @@ -1105,6 +1107,7 @@ def create_product( status: Optional[str] = None, active: Optional[bool] = None, product_id: Optional[str] = None, + product_base_type: Optional[str] = None, ) -> CreateOperation: """Create a new product. @@ -1112,7 +1115,6 @@ def create_product( project_name (str): Project name. name (str): Product name. product_base_type (str): Base type of the product, e.g. "render", - product_type (str): Product type. folder_id (str): Parent folder id. attrib (Optional[dict[str, Any]]): Product attributes. data (Optional[dict[str, Any]]): Product data. @@ -1121,6 +1123,7 @@ def create_product( active (Optional[bool]): Product active state. product_id (Optional[str]): Product id. If not passed new id is generated. + product_base_type (Optional[str]): Product base type. Returns: CreateOperation: Object of create operation. @@ -1131,16 +1134,17 @@ def create_product( create_data = { "id": product_id, "name": name, - "productBaseType": product_base_type, "productType": product_type, "folderId": folder_id, } + for key, value in ( ("attrib", attrib), ("data", data), ("tags", tags), ("status", status), ("active", active), + ("productBaseType", product_base_type) ): if value is not None: create_data[key] = value From e3bc2c52f28c3048d8d9834994786af9337ff638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 10 Jun 2025 13:18:45 +0200 Subject: [PATCH 5/7] :sparkles: add product base type getters to init and linting --- ayon_api/__init__.py | 3 +++ ayon_api/graphql_queries.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 6c70e855f..a2201ebc1 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -430,6 +430,9 @@ "get_product_types", "get_project_product_types", "get_product_type_names", + "get_product_base_types", + "get_project_product_base_types", + "get_product_base_type_names", "create_product", "update_product", "delete_product", diff --git a/ayon_api/graphql_queries.py b/ayon_api/graphql_queries.py index 75938a7e9..e5cf26360 100644 --- a/ayon_api/graphql_queries.py +++ b/ayon_api/graphql_queries.py @@ -344,7 +344,8 @@ def products_graphql_query(fields): product_names_var = query.add_variable("productNames", "[String!]") folder_ids_var = query.add_variable("folderIds", "[String!]") product_types_var = query.add_variable("productTypes", "[String!]") - product_base_types_var = query.add_variable("productBaseTypes", "[String!]") + product_base_types_var = query.add_variable( + "productBaseTypes", "[String!]") product_name_regex_var = query.add_variable("productNameRegex", "String!") product_path_regex_var = query.add_variable("productPathRegex", "String!") statuses_var = query.add_variable("productStatuses.", "[String!]") From 3a96dd614a149b888fd779bb817d970bdd450450 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:47:11 +0200 Subject: [PATCH 6/7] fix used function to create query --- ayon_api/server_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 70e411425..a7e5b60f0 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -61,6 +61,7 @@ project_product_base_types_query, project_product_types_query, product_types_query, + product_base_types_query, folders_graphql_query, tasks_graphql_query, tasks_by_folder_paths_graphql_query, @@ -6095,7 +6096,7 @@ def get_product_base_types( if not fields: fields = self.get_default_fields_for_type("productBaseType") - query = product_types_query(fields) + query = product_base_types_query(fields) parsed_data = query.query(self) From cb4a9f9045ceb1b587acd02e96ea7d969ae45d1d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:51:20 +0200 Subject: [PATCH 7/7] remove unnecessary 'get_product_base_type_names' function --- ayon_api/server_api.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index a7e5b60f0..f3f70619b 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -6132,43 +6132,6 @@ def get_project_product_base_types( return parsed_data.get("project", {}).get("productBaseTypes", []) - def get_product_base_type_names( - self, - project_name: Optional[str] = None, - product_ids: Optional[Iterable[str]] = None, - ) -> Set[str]: - """Get projects roduct base type names. - - Args: - project_name (Optional[str]): Name of project where to look for - queried entities. - product_ids (Optional[Iterable[str]]): Product ids filter. Can be - used only with 'project_name'. - - Returns: - set[str]: Product base type names used in the project. - - """ - if project_name and product_ids: - products = self.get_products( - project_name, - product_ids=product_ids, - fields=["productBaseType"], - active=None, - ) - return { - product["productBaseType"] - for product in products - } - - return { - product_info["name"] - for product_info in self.get_project_product_base_types( - project_name, fields=["name"] - ) - } - - def create_product( self, project_name: str,