Skip to content

🏛️Product base types: add support for product base types in API #255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ayon_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -427,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",
Expand Down
84 changes: 80 additions & 4 deletions ayon_api/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
FlatFolderDict,
ProjectHierarchyDict,
ProductTypeDict,
ProductBaseTypeDict,
StreamType,
)

Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions ayon_api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
"color",
}

# --- Product base type ---
DEFAULT_PRODUCT_BASE_TYPE_FIELDS = {
"name",
"icon",
"color",
}

# --- Project ---
DEFAULT_PROJECT_FIELDS = {
"active",
Expand Down Expand Up @@ -98,6 +105,7 @@
"folderId",
"active",
"productType",
"productBaseType",
"data",
"status",
"tags",
Expand Down
30 changes: 29 additions & 1 deletion ayon_api/entity_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Create a task object and add it to the entity hub.
"""Create a product 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.
Expand All @@ -458,13 +460,19 @@ 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.

"""
product_entity = ProductEntity(
name=name,
product_type=product_type,
product_base_type=product_base_type,
folder_id=folder_id,
tags=tags,
attribs=attribs,
Expand Down Expand Up @@ -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"]
Expand All @@ -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,
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
product_base_type=product["productBaseType"],
product_base_type=product.get("productBaseType"),

folder_id=product["folderId"],
tags=product["tags"],
attribs=product["attrib"],
Expand All @@ -3492,6 +3519,7 @@ def to_create_body_data(self):
output = {
"name": self.name,
"productType": self.product_type,
"productBaseType": self.product_base_type,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key should be added only if product base type is supported on server.

"folderId": self.parent_id,
}

Expand Down
49 changes: 49 additions & 0 deletions ayon_api/graphql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -143,6 +165,30 @@ def project_product_types_query(fields):
return query


def project_product_base_types_query(fields):
Copy link
Member

@iLLiCiTiT iLLiCiTiT Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't think this should have separate query, it should be added to project query and it does not need special method (meaning get_project_product_base_types).

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!")
Expand Down Expand Up @@ -298,6 +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_name_regex_var = query.add_variable("productNameRegex", "String!")
product_path_regex_var = query.add_variable("productPathRegex", "String!")
statuses_var = query.add_variable("productStatuses.", "[String!]")
Expand All @@ -311,6 +359,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)
Expand Down
Loading