AYON: Small fixes (#4841)

* us constants from ayon api for environments

* updated ayon api

* define 'BUILTIN_OCIO_ROOT' in ayon start script

* fox missing modules settings

* use window open on QApplication exec

* fix graphql queries

* implemented 'get_archived_assets'
This commit is contained in:
Jakub Trllo 2023-04-14 16:30:50 +02:00 committed by Jakub Trllo
parent 20bb87bf7d
commit 899f9965e4
18 changed files with 1550 additions and 340 deletions

View file

@ -74,7 +74,6 @@ if "--headless" in sys.argv:
elif os.getenv("OPENPYPE_HEADLESS_MODE") != "1":
os.environ.pop("OPENPYPE_HEADLESS_MODE", None)
IS_BUILT_APPLICATION = getattr(sys, "frozen", False)
HEADLESS_MODE_ENABLED = os.environ.get("OPENPYPE_HEADLESS_MODE") == "1"
SILENT_MODE_ENABLED = any(arg in _silent_commands for arg in sys.argv)
@ -137,6 +136,14 @@ os.environ["OPENPYPE_REPOS_ROOT"] = AYON_ROOT
os.environ["AVALON_LABEL"] = "AYON"
# Set name of pyblish UI import
os.environ["PYBLISH_GUI"] = "pyblish_pype"
# Set builtin OCIO root
os.environ["BUILTIN_OCIO_ROOT"] = os.path.join(
AYON_ROOT,
"vendor",
"bin",
"ocioconfig",
"OpenColorIOConfigs"
)
import blessed # noqa: E402
import certifi # noqa: E402
@ -183,6 +190,7 @@ if not os.getenv("SSL_CERT_FILE"):
elif os.getenv("SSL_CERT_FILE") != certifi.where():
_print("--- your system is set to use custom CA certificate bundle.")
from ayon_api.constants import SERVER_URL_ENV_KEY, SERVER_API_ENV_KEY
from ayon_common.connection.credentials import (
ask_to_login_ui,
add_server,
@ -252,12 +260,12 @@ def _connect_to_ayon_server():
if HEADLESS_MODE_ENABLED:
_print("!!! Cannot open v4 Login dialog in headless mode.")
_print((
"!!! Please use `AYON_SERVER_URL` to specify server address"
" and 'AYON_TOKEN' to specify user's token."
))
"!!! Please use `{}` to specify server address"
" and '{}' to specify user's token."
).format(SERVER_URL_ENV_KEY, SERVER_API_ENV_KEY))
sys.exit(1)
current_url = os.environ.get("AYON_SERVER_URL")
current_url = os.environ.get(SERVER_URL_ENV_KEY)
url, token, username = ask_to_login_ui(current_url, always_on_top=True)
if url is not None and token is not None:
confirm_server_login(url, token, username)
@ -345,10 +353,10 @@ def boot():
t.echo(i)
try:
cli.main(obj={}, prog_name="openpype")
cli.main(obj={}, prog_name="ayon")
except Exception: # noqa
exc_info = sys.exc_info()
_print("!!! OpenPype crashed:")
_print("!!! AYON crashed:")
traceback.print_exception(*exc_info)
sys.exit(1)

View file

@ -17,6 +17,7 @@ from typing import Optional, Union, Any
import ayon_api
from ayon_api.constants import SERVER_URL_ENV_KEY, SERVER_API_ENV_KEY
from ayon_api.exceptions import UrlError
from ayon_api.utils import (
validate_url,
@ -383,7 +384,7 @@ def load_environments():
"""Load environments on startup.
Handle environments needed for connection with server. Environments are
'AYON_SERVER_URL' and 'AYON_TOKEN'.
'AYON_SERVER_URL' and 'AYON_API_KEY'.
Server is looked up from environment. Already set environent is not
changed. If environemnt is not filled then last server stored in appdirs
@ -394,16 +395,16 @@ def load_environments():
based on server url.
"""
server_url = os.environ.get("AYON_SERVER_URL")
server_url = os.environ.get(SERVER_URL_ENV_KEY)
if not server_url:
server_url = get_last_server()
if not server_url:
return
os.environ["AYON_SERVER_URL"] = server_url
os.environ[SERVER_URL_ENV_KEY] = server_url
if not os.environ.get("AYON_TOKEN"):
if not os.environ.get(SERVER_API_ENV_KEY):
if token := load_token(server_url):
os.environ["AYON_TOKEN"] = token
os.environ[SERVER_API_ENV_KEY] = token
def set_environments(url: str, token: str):
@ -441,7 +442,7 @@ def need_server_or_login() -> bool:
bool: 'True' if server and token are available. Otherwise 'False'.
"""
server_url = os.environ.get("AYON_SERVER_URL")
server_url = os.environ.get(SERVER_URL_ENV_KEY)
if not server_url:
return True
@ -450,12 +451,14 @@ def need_server_or_login() -> bool:
except UrlError:
return True
token = os.environ.get("AYON_TOKEN")
token = os.environ.get(SERVER_API_ENV_KEY)
if token:
return not is_token_valid(server_url, token)
token = load_token(server_url)
return not is_token_valid(server_url, token)
if token:
return not is_token_valid(server_url, token)
return True
def confirm_server_login(url, token, username):

View file

@ -674,29 +674,14 @@ def ask_to_login(url=None, username=None, always_on_top=False):
if username:
window.set_username(username)
_output = {"out": None}
def _exec_window():
window.exec_()
result = window.result()
out_url, out_token, out_username, _logged_out = result
_output["out"] = out_url, out_token, out_username
return _output["out"]
# Use QTimer to exec dialog if application is not running yet
# - it is not possible to call 'exec_' on dialog without running app
# - it is but the window is stuck
if not app_instance.startingUp():
return _exec_window()
timer = QtCore.QTimer()
timer.setSingleShot(True)
timer.timeout.connect(_exec_window)
timer.start()
# This can become main Qt loop. Maybe should live elsewhere
app_instance.exec_()
return _output["out"]
window.exec_()
else:
window.open()
app_instance.exec_()
result = window.result()
out_url, out_token, out_username, _ = result
return out_url, out_token, out_username
def change_user(url, username, api_key, always_on_top=False):
@ -735,23 +720,10 @@ def change_user(url, username, api_key, always_on_top=False):
)
window.set_logged_in(True, url, username, api_key)
_output = {"out": None}
def _exec_window():
window.exec_()
_output["out"] = window.result()
return _output["out"]
# Use QTimer to exec dialog if application is not running yet
# - it is not possible to call 'exec_' on dialog without running app
# - it is but the window is stuck
if not app_instance.startingUp():
return _exec_window()
timer = QtCore.QTimer()
timer.setSingleShot(True)
timer.timeout.connect(_exec_window)
timer.start()
# This can become main Qt loop. Maybe should live elsewhere
app_instance.exec_()
return _output["out"]
window.exec_()
else:
window.open()
# This can become main Qt loop. Maybe should live elsewhere
app_instance.exec_()
return window.result()

View file

@ -216,8 +216,21 @@ def get_assets(
yield convert_v4_folder_to_v3(folder, project_name)
def get_archived_assets(*args, **kwargs):
raise NotImplementedError("'get_archived_assets' not implemented")
def get_archived_assets(
project_name,
asset_ids=None,
asset_names=None,
parent_ids=None,
fields=None
):
return get_assets(
project_name,
asset_ids,
asset_names,
parent_ids,
True,
fields
)
def get_asset_ids_with_subsets(project_name, asset_ids=None):

View file

@ -16,7 +16,7 @@ def folders_tasks_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
folders_field = project_field.add_field("folders", has_edges=True)
folders_field = project_field.add_field_with_edges("folders")
folders_field.set_filter("ids", folder_ids_var)
folders_field.set_filter("parentIds", parent_folder_ids_var)
folders_field.set_filter("names", folder_names_var)
@ -25,7 +25,7 @@ def folders_tasks_graphql_query(fields):
fields = set(fields)
fields.discard("tasks")
tasks_field = folders_field.add_field("tasks", has_edges=True)
tasks_field = folders_field.add_field_with_edges("tasks")
tasks_field.add_field("name")
tasks_field.add_field("taskType")

View file

@ -857,7 +857,7 @@ def delete_project(project_name, con=None):
return con.delete_project(project_name)
def create_thumbnail(project_name, src_filepath, con=None):
def create_thumbnail(project_name, src_filepath, thumbnail_id=None, con=None):
if con is None:
con = get_server_api_connection()
return con.create_thumbnail(project_name, src_filepath)
return con.create_thumbnail(project_name, src_filepath, thumbnail_id)

View file

@ -253,19 +253,6 @@ def _convert_royalrender_system_settings(ayon_settings, output):
def _convert_modules_system(
ayon_settings, output, addon_versions, default_settings
):
# TODO remove when not needed
# - these modules are not and won't be in AYON avaialble
for module_name in (
"addon_paths",
"avalon",
"job_queue",
"log_viewer",
"project_manager",
):
output["modules"][module_name] = (
default_settings["modules"][module_name]
)
# TODO add all modules
# TODO add 'enabled' values
for key, func in (
@ -282,6 +269,11 @@ def _convert_modules_system(
func(ayon_settings, output)
output_modules = output["modules"]
# TODO remove when not needed
for module_name, value in default_settings["modules"].items():
if module_name not in output_modules:
output_modules[module_name] = value
for module_name, value in default_settings["modules"].items():
if "enabled" not in value or module_name not in output_modules:
continue

View file

@ -56,6 +56,7 @@ from ._api import (
query_graphql,
get_addons_info,
get_addon_url,
download_addon_private_file,
get_dependencies_info,
@ -121,11 +122,35 @@ from ._api import (
get_representation_parents,
get_repre_ids_by_context_filters,
create_thumbnail,
get_thumbnail,
get_folder_thumbnail,
get_version_thumbnail,
get_workfile_thumbnail,
create_thumbnail,
update_thumbnail,
get_full_link_type_name,
get_link_types,
get_link_type,
create_link_type,
delete_link_type,
make_sure_link_type_exists,
create_link,
delete_link,
get_entities_links,
get_folder_links,
get_folders_links,
get_task_links,
get_tasks_links,
get_subset_links,
get_subsets_links,
get_version_links,
get_versions_links,
get_representations_links,
get_representation_links,
send_batch_operations,
)
@ -184,6 +209,7 @@ __all__ = (
"query_graphql",
"get_addons_info",
"get_addon_url",
"download_addon_private_file",
"get_dependencies_info",
@ -248,9 +274,33 @@ __all__ = (
"get_representation_parents",
"get_repre_ids_by_context_filters",
"create_thumbnail",
"get_thumbnail",
"get_folder_thumbnail",
"get_version_thumbnail",
"get_workfile_thumbnail",
"create_thumbnail",
"update_thumbnail",
"get_full_link_type_name",
"get_link_types",
"get_link_type",
"create_link_type",
"delete_link_type",
"make_sure_link_type_exists",
"create_link",
"delete_link",
"get_entities_links",
"get_folder_links",
"get_folders_links",
"get_task_links",
"get_tasks_links",
"get_subset_links",
"get_subsets_links",
"get_version_links",
"get_versions_links",
"get_representations_links",
"get_representation_links",
"send_batch_operations",
)

View file

@ -11,7 +11,7 @@ import socket
from .constants import (
SERVER_URL_ENV_KEY,
SERVER_TOKEN_ENV_KEY,
SERVER_API_ENV_KEY,
)
from .server_api import ServerAPI
from .exceptions import FailedServiceInit
@ -21,7 +21,7 @@ class GlobalServerAPI(ServerAPI):
"""Extended server api which also handles storing tokens and url.
Created object expect to have set environment variables
'AYON_SERVER_URL'. Also is expecting filled 'AYON_TOKEN'
'AYON_SERVER_URL'. Also is expecting filled 'AYON_API_KEY'
but that can be filled afterwards with calling 'login' method.
"""
@ -44,7 +44,7 @@ class GlobalServerAPI(ServerAPI):
previous_token = self._access_token
super(GlobalServerAPI, self).login(username, password)
if self.has_valid_token and previous_token != self._access_token:
os.environ[SERVER_TOKEN_ENV_KEY] = self._access_token
os.environ[SERVER_API_ENV_KEY] = self._access_token
@staticmethod
def get_url():
@ -52,7 +52,7 @@ class GlobalServerAPI(ServerAPI):
@staticmethod
def get_token():
return os.environ.get(SERVER_TOKEN_ENV_KEY)
return os.environ.get(SERVER_API_ENV_KEY)
@staticmethod
def set_environments(url, token):
@ -64,7 +64,7 @@ class GlobalServerAPI(ServerAPI):
"""
os.environ[SERVER_URL_ENV_KEY] = url or ""
os.environ[SERVER_TOKEN_ENV_KEY] = token or ""
os.environ[SERVER_API_ENV_KEY] = token or ""
class GlobalContext:
@ -151,7 +151,7 @@ class ServiceContext:
connect=True
):
token = cls.get_value_from_envs(
("AY_API_KEY", "AYON_TOKEN"),
("AY_API_KEY", "AYON_API_KEY"),
token
)
server_url = cls.get_value_from_envs(
@ -322,7 +322,7 @@ def get_server_api_connection():
"""Access to global scope object of GlobalServerAPI.
This access expect to have set environment variables 'AYON_SERVER_URL'
and 'AYON_TOKEN'.
and 'AYON_API_KEY'.
Returns:
GlobalServerAPI: Object of connection to server.
@ -521,6 +521,11 @@ def get_addons_info(*args, **kwargs):
return con.get_addons_info(*args, **kwargs)
def get_addon_url(addon_name, addon_version, *subpaths):
con = get_server_api_connection()
return con.get_addon_url(addon_name, addon_version, *subpaths)
def download_addon_private_file(*args, **kwargs):
con = get_server_api_connection()
return con.download_addon_private_file(*args, **kwargs)
@ -776,11 +781,6 @@ def delete_project(project_name):
return con.delete_project(project_name)
def create_thumbnail(project_name, src_filepath):
con = get_server_api_connection()
return con.create_thumbnail(project_name, src_filepath)
def get_thumbnail(project_name, entity_type, entity_id, thumbnail_id=None):
con = get_server_api_connection()
con.get_thumbnail(project_name, entity_type, entity_id, thumbnail_id)
@ -801,11 +801,259 @@ def get_workfile_thumbnail(project_name, workfile_id, thumbnail_id=None):
return con.get_workfile_thumbnail(project_name, workfile_id, thumbnail_id)
def create_thumbnail(project_name, src_filepath):
def create_thumbnail(project_name, src_filepath, thumbnail_id=None):
con = get_server_api_connection()
return con.create_thumbnail(project_name, src_filepath)
return con.create_thumbnail(project_name, src_filepath, thumbnail_id)
def update_thumbnail(project_name, thumbnail_id, src_filepath):
con = get_server_api_connection()
return con.update_thumbnail(project_name, thumbnail_id, src_filepath)
def get_default_fields_for_type(entity_type):
con = get_server_api_connection()
return con.get_default_fields_for_type(entity_type)
def get_full_link_type_name(link_type_name, input_type, output_type):
con = get_server_api_connection()
return con.get_full_link_type_name(
link_type_name, input_type, output_type)
def get_link_types(project_name):
con = get_server_api_connection()
return con.get_link_types(project_name)
def get_link_type(project_name, link_type_name, input_type, output_type):
con = get_server_api_connection()
return con.get_link_type(
project_name, link_type_name, input_type, output_type)
def create_link_type(
project_name, link_type_name, input_type, output_type, data=None):
con = get_server_api_connection()
return con.create_link_type(
project_name, link_type_name, input_type, output_type, data=data)
def delete_link_type(project_name, link_type_name, input_type, output_type):
con = get_server_api_connection()
return con.delete_link_type(
project_name, link_type_name, input_type, output_type)
def make_sure_link_type_exists(
project_name, link_type_name, input_type, output_type, data=None
):
con = get_server_api_connection()
return con.make_sure_link_type_exists(
project_name, link_type_name, input_type, output_type, data=data
)
def create_link(
project_name,
link_type_name,
input_id,
input_type,
output_id,
output_type
):
con = get_server_api_connection()
return con.create_link(
project_name,
link_type_name,
input_id, input_type,
output_id, output_type
)
def delete_link(project_name, link_id):
con = get_server_api_connection()
return con.delete_link(project_name, link_id)
def get_entities_links(
project_name,
entity_type,
entity_ids=None,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_entities_links(
project_name,
entity_type,
entity_ids,
link_types,
link_direction
)
def get_folders_links(
project_name,
folder_ids=None,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_folders_links(
project_name,
folder_ids,
link_types,
link_direction
)
def get_folder_links(
project_name,
folder_id,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_folder_links(
project_name,
folder_id,
link_types,
link_direction
)
def get_tasks_links(
project_name,
task_ids=None,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_tasks_links(
project_name,
task_ids,
link_types,
link_direction
)
def get_task_links(
project_name,
task_id,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_task_links(
project_name,
task_id,
link_types,
link_direction
)
def get_subsets_links(
project_name,
subset_ids=None,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_subsets_links(
project_name,
subset_ids,
link_types,
link_direction
)
def get_subset_links(
project_name,
subset_id,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_subset_links(
project_name,
subset_id,
link_types,
link_direction
)
def get_versions_links(
project_name,
version_ids=None,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_versions_links(
project_name,
version_ids,
link_types,
link_direction
)
def get_version_links(
project_name,
version_id,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_version_links(
project_name,
version_id,
link_types,
link_direction
)
def get_representations_links(
project_name,
representation_ids=None,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_representations_links(
project_name,
representation_ids,
link_types,
link_direction
)
def get_representation_links(
project_name,
representation_id,
link_types=None,
link_direction=None
):
con = get_server_api_connection()
return con.get_representation_links(
project_name,
representation_id,
link_types,
link_direction
)
def send_batch_operations(
project_name,
operations,
can_fail=False,
raise_on_fail=True
):
con = get_server_api_connection()
return con.send_batch_operations(
project_name,
operations,
can_fail=can_fail,
raise_on_fail=raise_on_fail
)

View file

@ -1,5 +1,8 @@
# Environments where server url and api key are stored for global connection
SERVER_URL_ENV_KEY = "AYON_SERVER_URL"
SERVER_TOKEN_ENV_KEY = "AYON_TOKEN"
SERVER_API_ENV_KEY = "AYON_API_KEY"
# Backwards compatibility
SERVER_TOKEN_ENV_KEY = SERVER_API_ENV_KEY
# --- Project ---
DEFAULT_PROJECT_FIELDS = {
@ -102,4 +105,4 @@ DEFAULT_EVENT_FIELDS = {
"topic",
"updatedAt",
"user",
}
}

View file

@ -1,6 +1,6 @@
import copy
import collections
from abc import ABCMeta, abstractmethod, abstractproperty
from abc import ABCMeta, abstractmethod
import six
from ._api import get_server_api_connection
@ -52,17 +52,35 @@ class EntityHub(object):
@property
def allow_data_changes(self):
"""Entity hub allows changes of 'data' key on entities."""
"""Entity hub allows changes of 'data' key on entities.
Data are private and not all users may have access to them. Also to get
'data' for entity is required to use REST api calls, which means to
query each entity on-by-one from server.
Returns:
bool: Data changes are allowed.
"""
return self._allow_data_changes
@property
def project_name(self):
"""Project name which is maintained by hub.
Returns:
str: Name of project.
"""
return self._project_name
@property
def project_entity(self):
"""Project entity."""
"""Project entity.
Returns:
ProjectEntity: Project entity.
"""
if self._project_entity is UNKNOWN_VALUE:
self.fill_project_from_server()
@ -187,6 +205,12 @@ class EntityHub(object):
@property
def entities(self):
"""Iterator over available entities.
Returns:
Iterator[BaseEntity]: All queried/created entities cached in hub.
"""
for entity in self._entities_by_id.values():
yield entity
@ -194,8 +218,21 @@ class EntityHub(object):
"""Create folder object and add it to entity hub.
Args:
parent (Union[ProjectEntity, FolderEntity]): Parent of added
folder.
folder_type (str): Type of folder. Folder type must be available in
config of project folder types.
entity_id (Union[str, None]): Id of the entity. New id is created if
not passed.
parent_id (Union[str, None]): Id of parent entity.
name (str): Name of entity.
label (Optional[str]): Folder label.
path (Optional[str]): Folder path. Path consist of all parent names
with slash('/') used as separator.
attribs (Dict[str, Any]): Attribute values.
data (Dict[str, Any]): Entity data (custom data).
thumbnail_id (Union[str, None]): Id of entity's thumbnail.
active (bool): Is entity active.
created (Optional[bool]): Entity is new. When 'None' is passed the
value is defined based on value of 'entity_id'.
Returns:
FolderEntity: Added folder entity.
@ -208,6 +245,27 @@ class EntityHub(object):
return folder_entity
def add_new_task(self, *args, created=True, **kwargs):
"""Create folder object and add it to entity hub.
Args:
task_type (str): Type of task. Task type must be available in
config of project folder types.
entity_id (Union[str, None]): Id of the entity. New id is created if
not passed.
parent_id (Union[str, None]): Id of parent entity.
name (str): Name of entity.
label (Optional[str]): Folder label.
attribs (Dict[str, Any]): Attribute values.
data (Dict[str, Any]): Entity data (custom data).
thumbnail_id (Union[str, None]): Id of entity's thumbnail.
active (bool): Is entity active.
created (Optional[bool]): Entity is new. When 'None' is passed the
value is defined based on value of 'entity_id'.
Returns:
TaskEntity: Added task entity.
"""
task_entity = TaskEntity(
*args, **kwargs, created=created, entity_hub=self
)
@ -428,7 +486,7 @@ class EntityHub(object):
if parent_id is None:
return
parent = self._entities_by_parent_id.get(parent_id)
parent = self._entities_by_id.get(parent_id)
if parent is not None:
parent.remove_child(entity.id)
@ -459,10 +517,12 @@ class EntityHub(object):
reset_queue.append(child.id)
def fill_project_from_server(self):
"""Query project from server and create it's entity.
"""Query project data from server and create project entity.
This method will invalidate previous object of Project entity.
Returns:
ProjectEntity: Entity that was created based on queried data.
ProjectEntity: Entity that was updated with server data.
Raises:
ValueError: When project was not found on server.
@ -844,17 +904,17 @@ class BaseEntity(object):
entity are set as "current data" on server.
Args:
entity_id (Union[str, None]): Id of the entity. New id is created if
not passed.
parent_id (Union[str, None]): Id of parent entity.
name (str): Name of entity.
attribs (Dict[str, Any]): Attribute values.
data (Dict[str, Any]): Entity data (custom data).
parent_id (Union[str, None]): Id of parent entity.
entity_id (Union[str, None]): Id of the entity. New id is created if
not passed.
thumbnail_id (Union[str, None]): Id of entity's thumbnail.
active (bool): Is entity active.
entity_hub (EntityHub): Object of entity hub which created object of
the entity.
created (Union[bool, None]): Entity is new. When 'None' is passed the
created (Optional[bool]): Entity is new. When 'None' is passed the
value is defined based on value of 'entity_id'.
"""
@ -981,7 +1041,8 @@ class BaseEntity(object):
return self._entity_hub.project_name
@abstractproperty
@property
@abstractmethod
def entity_type(self):
"""Entity type coresponding to server.
@ -991,7 +1052,8 @@ class BaseEntity(object):
pass
@abstractproperty
@property
@abstractmethod
def parent_entity_types(self):
"""Entity type coresponding to server.
@ -1001,7 +1063,8 @@ class BaseEntity(object):
pass
@abstractproperty
@property
@abstractmethod
def changes(self):
"""Receive entity changes.
@ -1331,6 +1394,27 @@ class BaseEntity(object):
class ProjectEntity(BaseEntity):
"""Entity representing project on AYON server.
Args:
project_code (str): Project code.
library (bool): Is project library project.
folder_types (list[dict[str, Any]]): Folder types definition.
task_types (list[dict[str, Any]]): Task types definition.
entity_id (Optional[str]): Id of the entity. New id is created if
not passed.
parent_id (Union[str, None]): Id of parent entity.
name (str): Name of entity.
attribs (Dict[str, Any]): Attribute values.
data (Dict[str, Any]): Entity data (custom data).
thumbnail_id (Union[str, None]): Id of entity's thumbnail.
active (bool): Is entity active.
entity_hub (EntityHub): Object of entity hub which created object of
the entity.
created (Optional[bool]): Entity is new. When 'None' is passed the
value is defined based on value of 'entity_id'.
"""
entity_type = "project"
parent_entity_types = []
# TODO These are hardcoded but maybe should be used from server???
@ -1433,6 +1517,28 @@ class ProjectEntity(BaseEntity):
class FolderEntity(BaseEntity):
"""Entity representing a folder on AYON server.
Args:
folder_type (str): Type of folder. Folder type must be available in
config of project folder types.
entity_id (Union[str, None]): Id of the entity. New id is created if
not passed.
parent_id (Union[str, None]): Id of parent entity.
name (str): Name of entity.
attribs (Dict[str, Any]): Attribute values.
data (Dict[str, Any]): Entity data (custom data).
thumbnail_id (Union[str, None]): Id of entity's thumbnail.
active (bool): Is entity active.
label (Optional[str]): Folder label.
path (Optional[str]): Folder path. Path consist of all parent names
with slash('/') used as separator.
entity_hub (EntityHub): Object of entity hub which created object of
the entity.
created (Optional[bool]): Entity is new. When 'None' is passed the
value is defined based on value of 'entity_id'.
"""
entity_type = "folder"
parent_entity_types = ["folder", "project"]
@ -1587,6 +1693,26 @@ class FolderEntity(BaseEntity):
class TaskEntity(BaseEntity):
"""Entity representing a task on AYON server.
Args:
task_type (str): Type of task. Task type must be available in config
of project task types.
entity_id (Union[str, None]): Id of the entity. New id is created if
not passed.
parent_id (Union[str, None]): Id of parent entity.
name (str): Name of entity.
label (Optional[str]): Task label.
attribs (Dict[str, Any]): Attribute values.
data (Dict[str, Any]): Entity data (custom data).
thumbnail_id (Union[str, None]): Id of entity's thumbnail.
active (bool): Is entity active.
entity_hub (EntityHub): Object of entity hub which created object of
the entity.
created (Optional[bool]): Entity is new. When 'None' is passed the
value is defined based on value of 'entity_id'.
"""
entity_type = "task"
parent_entity_types = ["folder"]

View file

@ -49,4 +49,4 @@ class ServerEvent(object):
"payload": self.payload,
"finished": self.finished,
"store": self.store
}
}

View file

@ -33,6 +33,16 @@ class ServerNotReached(ServerError):
pass
class RequestError(Exception):
def __init__(self, message, response):
self.response = response
super(RequestError, self).__init__(message)
class HTTPRequestError(RequestError):
pass
class GraphQlQueryFailed(Exception):
def __init__(self, errors, query, variables):
if variables is None:
@ -94,4 +104,4 @@ class FailedOperations(Exception):
class FailedServiceInit(Exception):
pass
pass

View file

@ -1,6 +1,6 @@
import copy
import numbers
from abc import ABCMeta, abstractproperty, abstractmethod
from abc import ABCMeta, abstractmethod
import six
@ -232,21 +232,31 @@ class GraphQlQuery:
self._children.append(field)
field.set_parent(self)
def add_field(self, name, has_edges=None):
def add_field_with_edges(self, name):
"""Add field with edges to query.
Args:
name (str): Field name e.g. 'tasks'.
Returns:
GraphQlQueryEdgeField: Created field object.
"""
item = GraphQlQueryEdgeField(name, self)
self.add_obj_field(item)
return item
def add_field(self, name):
"""Add field to query.
Args:
name (str): Field name e.g. 'id'.
has_edges (bool): Field has edges so it need paging.
Returns:
BaseGraphQlQueryField: Created field object.
GraphQlQueryField: Created field object.
"""
if has_edges:
item = GraphQlQueryEdgeField(name, self)
else:
item = GraphQlQueryField(name, self)
item = GraphQlQueryField(name, self)
self.add_obj_field(item)
return item
@ -376,7 +386,6 @@ class BaseGraphQlQueryField(object):
name (str): Name of field.
parent (Union[BaseGraphQlQueryField, GraphQlQuery]): Parent object of a
field.
has_edges (bool): Field has edges and should handle paging.
"""
def __init__(self, name, parent):
@ -401,6 +410,36 @@ class BaseGraphQlQueryField(object):
def __repr__(self):
return "<{} {}>".format(self.__class__.__name__, self.path)
def add_variable(self, key, value_type, value=None):
"""Add variable to query.
Args:
key (str): Variable name.
value_type (str): Type of expected value in variables. This is
graphql type e.g. "[String!]", "Int", "Boolean", etc.
value (Any): Default value for variable. Can be changed later.
Returns:
QueryVariable: Created variable object.
Raises:
KeyError: If variable was already added before.
"""
return self._parent.add_variable(key, value_type, value)
def get_variable(self, key):
"""Variable object.
Args:
key (str): Variable name added to headers.
Returns:
QueryVariable: Variable object used in query string.
"""
return self._parent.get_variable(key)
@property
def need_query(self):
"""Still need query from server.
@ -414,11 +453,21 @@ class BaseGraphQlQueryField(object):
if self._need_query:
return True
for child in self._children:
for child in self._children_iter():
if child.need_query:
return True
return False
def _children_iter(self):
"""Iterate over all children fields of object.
Returns:
Iterator[BaseGraphQlQueryField]: Children fields.
"""
for child in self._children:
yield child
def sum_edge_fields(self, max_limit=None):
"""Check how many edge fields query has.
@ -437,7 +486,7 @@ class BaseGraphQlQueryField(object):
if isinstance(self, GraphQlQueryEdgeField):
counter = 1
for child in self._children:
for child in self._children_iter():
counter += child.sum_edge_fields(max_limit)
if max_limit is not None and counter >= max_limit:
break
@ -451,7 +500,8 @@ class BaseGraphQlQueryField(object):
def indent(self):
return self._parent.child_indent + self.offset
@abstractproperty
@property
@abstractmethod
def child_indent(self):
pass
@ -459,13 +509,14 @@ class BaseGraphQlQueryField(object):
def query_item(self):
return self._query_item
@abstractproperty
@property
@abstractmethod
def has_edges(self):
pass
@property
def child_has_edges(self):
for child in self._children:
for child in self._children_iter():
if child.has_edges or child.child_has_edges:
return True
return False
@ -487,7 +538,7 @@ class BaseGraphQlQueryField(object):
return self._path
def reset_cursor(self):
for child in self._children:
for child in self._children_iter():
child.reset_cursor()
def get_variable_value(self, *args, **kwargs):
@ -518,11 +569,13 @@ class BaseGraphQlQueryField(object):
self._children.append(field)
field.set_parent(self)
def add_field(self, name, has_edges=None):
if has_edges:
item = GraphQlQueryEdgeField(name, self)
else:
item = GraphQlQueryField(name, self)
def add_field_with_edges(self, name):
item = GraphQlQueryEdgeField(name, self)
self.add_obj_field(item)
return item
def add_field(self, name):
item = GraphQlQueryField(name, self)
self.add_obj_field(item)
return item
@ -580,7 +633,7 @@ class BaseGraphQlQueryField(object):
def _fake_children_parse(self):
"""Mark children as they don't need query."""
for child in self._children:
for child in self._children_iter():
child.parse_result({}, {}, {})
@abstractmethod
@ -673,12 +726,38 @@ class GraphQlQueryEdgeField(BaseGraphQlQueryField):
def __init__(self, *args, **kwargs):
super(GraphQlQueryEdgeField, self).__init__(*args, **kwargs)
self._cursor = None
self._edge_children = []
@property
def child_indent(self):
offset = self.offset * 2
return self.indent + offset
def _children_iter(self):
for child in super(GraphQlQueryEdgeField, self)._children_iter():
yield child
for child in self._edge_children:
yield child
def add_obj_field(self, field):
if field in self._edge_children:
return
super(GraphQlQueryEdgeField, self).add_obj_field(field)
def add_obj_edge_field(self, field):
if field in self._edge_children or field in self._children:
return
self._edge_children.append(field)
field.set_parent(self)
def add_edge_field(self, name):
item = GraphQlQueryField(name, self)
self.add_obj_edge_field(item)
return item
def reset_cursor(self):
# Reset cursor only for edges
self._cursor = None
@ -733,6 +812,9 @@ class GraphQlQueryEdgeField(BaseGraphQlQueryField):
nodes_by_cursor[edge_cursor] = edge_value
node_values.append(edge_value)
for child in self._edge_children:
child.parse_result(edge, edge_value, progress_data)
for child in self._children:
child.parse_result(edge["node"], edge_value, progress_data)
@ -740,12 +822,12 @@ class GraphQlQueryEdgeField(BaseGraphQlQueryField):
return
change_cursor = True
for child in self._children:
for child in self._children_iter():
if child.need_query:
change_cursor = False
if change_cursor:
for child in self._children:
for child in self._children_iter():
child.reset_cursor()
self._cursor = new_cursor
@ -761,7 +843,7 @@ class GraphQlQueryEdgeField(BaseGraphQlQueryField):
return filters
def calculate_query(self):
if not self._children:
if not self._children and not self._edge_children:
raise ValueError("Missing child definitions for edges {}".format(
self.path
))
@ -779,16 +861,21 @@ class GraphQlQueryEdgeField(BaseGraphQlQueryField):
edges_offset = offset + self.offset * " "
node_offset = edges_offset + self.offset * " "
output.append(edges_offset + "edges {")
output.append(node_offset + "node {")
for field in self._edge_children:
output.append(field.calculate_query())
for field in self._children:
output.append(
field.calculate_query()
)
if self._children:
output.append(node_offset + "node {")
for field in self._children:
output.append(
field.calculate_query()
)
output.append(node_offset + "}")
if self.child_has_edges:
output.append(node_offset + "cursor")
output.append(node_offset + "}")
if self.child_has_edges:
output.append(node_offset + "cursor")
output.append(edges_offset + "}")
# Add page information

View file

@ -25,6 +25,58 @@ def fields_to_dict(fields):
return output
def add_links_fields(entity_field, nested_fields):
if "links" not in nested_fields:
return
links_fields = nested_fields.pop("links")
link_edge_fields = {
"id",
"linkType",
"projectName",
"entityType",
"entityId",
"direction",
"description",
"author",
}
if isinstance(links_fields, dict):
simple_fields = set(links_fields)
simple_variant = len(simple_fields - link_edge_fields) == 0
else:
simple_variant = True
simple_fields = link_edge_fields
link_field = entity_field.add_field_with_edges("links")
link_type_var = link_field.add_variable("linkTypes", "[String!]")
link_dir_var = link_field.add_variable("linkDirection", "String!")
link_field.set_filter("linkTypes", link_type_var)
link_field.set_filter("direction", link_dir_var)
if simple_variant:
for key in simple_fields:
link_field.add_edge_field(key)
return
query_queue = collections.deque()
for key, value in links_fields.items():
if key in link_edge_fields:
link_field.add_edge_field(key)
continue
query_queue.append((key, value, link_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))
def project_graphql_query(fields):
query = GraphQlQuery("ProjectQuery")
project_name_var = query.add_variable("projectName", "String!")
@ -51,7 +103,7 @@ def project_graphql_query(fields):
def projects_graphql_query(fields):
query = GraphQlQuery("ProjectsQuery")
projects_field = query.add_field("projects", has_edges=True)
projects_field = query.add_field_with_edges("projects")
nested_fields = fields_to_dict(fields)
@ -83,7 +135,7 @@ def folders_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
folders_field = project_field.add_field("folders", has_edges=True)
folders_field = project_field.add_field_with_edges("folders")
folders_field.set_filter("ids", folder_ids_var)
folders_field.set_filter("parentIds", parent_folder_ids_var)
folders_field.set_filter("names", folder_names_var)
@ -91,6 +143,7 @@ def folders_graphql_query(fields):
folders_field.set_filter("hasSubsets", has_subsets_var)
nested_fields = fields_to_dict(fields)
add_links_fields(folders_field, nested_fields)
query_queue = collections.deque()
for key, value in nested_fields.items():
@ -119,7 +172,7 @@ def tasks_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
tasks_field = project_field.add_field("tasks", has_edges=True)
tasks_field = project_field.add_field_with_edges("tasks")
tasks_field.set_filter("ids", task_ids_var)
# WARNING: At moment when this been created 'names' filter is not supported
tasks_field.set_filter("names", task_names_var)
@ -127,6 +180,7 @@ def tasks_graphql_query(fields):
tasks_field.set_filter("folderIds", folder_ids_var)
nested_fields = fields_to_dict(fields)
add_links_fields(tasks_field, nested_fields)
query_queue = collections.deque()
for key, value in nested_fields.items():
@ -155,12 +209,13 @@ def subsets_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
subsets_field = project_field.add_field("subsets", has_edges=True)
subsets_field = project_field.add_field_with_edges("subsets")
subsets_field.set_filter("ids", subset_ids_var)
subsets_field.set_filter("names", subset_names_var)
subsets_field.set_filter("folderIds", folder_ids_var)
nested_fields = fields_to_dict(set(fields))
add_links_fields(subsets_field, nested_fields)
query_queue = collections.deque()
for key, value in nested_fields.items():
@ -194,7 +249,7 @@ def versions_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
subsets_field = project_field.add_field("versions", has_edges=True)
subsets_field = project_field.add_field_with_edges("versions")
subsets_field.set_filter("ids", version_ids_var)
subsets_field.set_filter("subsetIds", subset_ids_var)
subsets_field.set_filter("versions", versions_var)
@ -203,6 +258,7 @@ def versions_graphql_query(fields):
subsets_field.set_filter("heroOrLatestOnly", hero_or_latest_only_var)
nested_fields = fields_to_dict(set(fields))
add_links_fields(subsets_field, nested_fields)
query_queue = collections.deque()
for key, value in nested_fields.items():
@ -231,12 +287,13 @@ def representations_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
repres_field = project_field.add_field("representations", has_edges=True)
repres_field = project_field.add_field_with_edges("representations")
repres_field.set_filter("ids", repre_ids_var)
repres_field.set_filter("versionIds", version_ids_var)
repres_field.set_filter("names", repre_names_var)
nested_fields = fields_to_dict(set(fields))
add_links_fields(repres_field, nested_fields)
query_queue = collections.deque()
for key, value in nested_fields.items():
@ -266,7 +323,7 @@ def representations_parents_qraphql_query(
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
repres_field = project_field.add_field("representations", has_edges=True)
repres_field = project_field.add_field_with_edges("representations")
repres_field.add_field("id")
repres_field.set_filter("ids", repre_ids_var)
version_field = repres_field.add_field("version")
@ -306,12 +363,13 @@ def workfiles_info_graphql_query(fields):
project_field = query.add_field("project")
project_field.set_filter("name", project_name_var)
workfiles_field = project_field.add_field("workfiles", has_edges=True)
workfiles_field = project_field.add_field_with_edges("workfiles")
workfiles_field.set_filter("ids", workfiles_info_ids)
workfiles_field.set_filter("taskIds", task_ids_var)
workfiles_field.set_filter("paths", paths_var)
nested_fields = fields_to_dict(set(fields))
add_links_fields(workfiles_field, nested_fields)
query_queue = collections.deque()
for key, value in nested_fields.items():
@ -337,7 +395,7 @@ def events_graphql_query(fields):
users_var = query.add_variable("eventUsers", "[String!]")
include_logs_var = query.add_variable("includeLogsFilter", "Boolean!")
events_field = query.add_field("events", has_edges=True)
events_field = query.add_field_with_edges("events")
events_field.set_filter("topics", topics_var)
events_field.set_filter("projects", projects_var)
events_field.set_filter("states", states_var)

View file

@ -1,7 +1,7 @@
import copy
import collections
import uuid
from abc import ABCMeta, abstractproperty
from abc import ABCMeta, abstractmethod
import six
@ -301,7 +301,8 @@ class AbstractOperation(object):
def entity_type(self):
return self._entity_type
@abstractproperty
@property
@abstractmethod
def operation_name(self):
"""Stringified type of operation."""

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,2 @@
"""Package declaring Python API for Ayon server."""
__version__ = "0.1.16"
__version__ = "0.1.17"