diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index e17d75c453..6e1e38d0b2 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -8,7 +8,7 @@ on: branches: [develop] types: [review_requested, ready_for_review] paths-ignore: - - 'docs/**', + - 'docs/**' - 'website/**' - 'vendor/**' diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 2f305e24e3..f624b96125 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -14,7 +14,10 @@ from zipfile import ZipFile, BadZipFile from appdirs import user_data_dir from speedcopy import copyfile -from .user_settings import OpenPypeSettingsRegistry +from .user_settings import ( + OpenPypeSecureRegistry, + OpenPypeSettingsRegistry +) from .tools import get_openpype_path_from_db @@ -239,6 +242,7 @@ class BootstrapRepos: self._app = "openpype" self._log = log.getLogger(str(__class__)) self.data_dir = Path(user_data_dir(self._app, self._vendor)) + self.secure_registry = OpenPypeSecureRegistry("mongodb") self.registry = OpenPypeSettingsRegistry() self.zip_filter = [".pyc", "__pycache__"] self.openpype_filter = [ diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index 2cc0ed8448..27b2d1fe37 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -13,7 +13,7 @@ from .tools import ( validate_mongo_connection, get_openpype_path_from_db ) -from .user_settings import OpenPypeSettingsRegistry +from .user_settings import OpenPypeSecureRegistry from .version import __version__ @@ -42,13 +42,13 @@ class InstallDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(InstallDialog, self).__init__(parent) - self.registry = OpenPypeSettingsRegistry() + self.secure_registry = OpenPypeSecureRegistry("mongodb") self.mongo_url = "" try: self.mongo_url = ( os.getenv("OPENPYPE_MONGO", "") - or self.registry.get_secure_item("openPypeMongo") + or self.secure_registry.get_item("openPypeMongo") ) except ValueError: pass diff --git a/igniter/install_thread.py b/igniter/install_thread.py index bf5d541056..df8b830209 100644 --- a/igniter/install_thread.py +++ b/igniter/install_thread.py @@ -71,7 +71,7 @@ class InstallThread(QThread): if not os.getenv("OPENPYPE_MONGO"): # try to get it from settings registry try: - self._mongo = bs.registry.get_secure_item( + self._mongo = bs.secure_registry.get_item( "openPypeMongo") except ValueError: self.message.emit( @@ -82,7 +82,7 @@ class InstallThread(QThread): self._mongo = os.getenv("OPENPYPE_MONGO") else: self.message.emit("Saving mongo connection string ...", False) - bs.registry.set_secure_item("openPypeMongo", self._mongo) + bs.secure_registry.set_item("openPypeMongo", self._mongo) os.environ["OPENPYPE_MONGO"] = self._mongo @@ -169,7 +169,7 @@ class InstallThread(QThread): f"!!! invalid mongo url {self._mongo}", True) self.finished.emit(InstallResult(-1)) return - bs.registry.set_secure_item("openPypeMongo", self._mongo) + bs.secure_registry.set_item("openPypeMongo", self._mongo) os.environ["OPENPYPE_MONGO"] = self._mongo self.message.emit(f"processing {self._path}", True) diff --git a/igniter/user_settings.py b/igniter/user_settings.py index 77fb8b5ae5..2a406f83dd 100644 --- a/igniter/user_settings.py +++ b/igniter/user_settings.py @@ -25,8 +25,112 @@ except ImportError: import platform -import appdirs import six +import appdirs + +_PLACEHOLDER = object() + + +class OpenPypeSecureRegistry: + """Store information using keyring. + + Registry should be used for private data that should be available only for + user. + + All passed registry names will have added prefix `OpenPype/` to easier + identify which data were created by OpenPype. + + Args: + name(str): Name of registry used as identifier for data. + """ + def __init__(self, name): + try: + import keyring + + except Exception: + raise NotImplementedError( + "Python module `keyring` is not available." + ) + + # hack for cx_freeze and Windows keyring backend + if platform.system().lower() == "windows": + from keyring.backends import Windows + + keyring.set_keyring(Windows.WinVaultKeyring()) + + # Force "OpenPype" prefix + self._name = "/".join(("OpenPype", name)) + + def set_item(self, name, value): + # type: (str, str) -> None + """Set sensitive item into system's keyring. + + This uses `Keyring module`_ to save sensitive stuff into system's + keyring. + + Args: + name (str): Name of the item. + value (str): Value of the item. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + keyring.set_password(self._name, name, value) + + @lru_cache(maxsize=32) + def get_item(self, name, default=_PLACEHOLDER): + """Get value of sensitive item from system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item. + default (Any): Default value if item is not available. + + Returns: + value (str): Value of the item. + + Raises: + ValueError: If item doesn't exist and default is not defined. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + value = keyring.get_password(self._name, name) + if value: + return value + + if default is not _PLACEHOLDER: + return default + + # NOTE Should raise `KeyError` + raise ValueError( + "Item {}:{} does not exist in keyring.".format(self._name, name) + ) + + def delete_item(self, name): + # type: (str) -> None + """Delete value stored in system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item to be deleted. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + self.get_item.cache_clear() + keyring.delete_password(self._name, name) @six.add_metaclass(ABCMeta) @@ -46,13 +150,6 @@ class ASettingRegistry(): # type: (str) -> ASettingRegistry super(ASettingRegistry, self).__init__() - if six.PY3: - import keyring - # hack for cx_freeze and Windows keyring backend - if platform.system() == "Windows": - from keyring.backends import Windows - keyring.set_keyring(Windows.WinVaultKeyring()) - self._name = name self._items = {} @@ -127,78 +224,6 @@ class ASettingRegistry(): del self._items[name] self._delete_item(name) - def set_secure_item(self, name, value): - # type: (str, str) -> None - """Set sensitive item into system's keyring. - - This uses `Keyring module`_ to save sensitive stuff into system's - keyring. - - Args: - name (str): Name of the item. - value (str): Value of the item. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - keyring.set_password(self._name, name, value) - - @lru_cache(maxsize=32) - def get_secure_item(self, name): - # type: (str) -> str - """Get value of sensitive item from system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item. - - Returns: - value (str): Value of the item. - - Raises: - ValueError: If item doesn't exist. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - value = keyring.get_password(self._name, name) - if not value: - raise ValueError( - "Item {}:{} does not exist in keyring.".format( - self._name, name)) - return value - - def delete_secure_item(self, name): - # type: (str) -> None - """Delete value stored in system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item to be deleted. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - self.get_secure_item.cache_clear() - keyring.delete_password(self._name, name) - class IniSettingRegistry(ASettingRegistry): """Class using :mod:`configparser`. @@ -459,9 +484,10 @@ class OpenPypeSettingsRegistry(JSONSettingRegistry): """ - def __init__(self): + def __init__(self, name=None): self.vendor = "pypeclub" self.product = "openpype" + if not name: + name = "openpype_settings" path = appdirs.user_data_dir(self.product, self.vendor) - super(OpenPypeSettingsRegistry, self).__init__( - "openpype_settings", path) + super(OpenPypeSettingsRegistry, self).__init__(name, path) diff --git a/openpype/hooks/pre_python2_vendor.py b/openpype/hooks/pre_python2_vendor.py index 7aaf713dec..815682fef8 100644 --- a/openpype/hooks/pre_python2_vendor.py +++ b/openpype/hooks/pre_python2_vendor.py @@ -6,7 +6,7 @@ class PrePython2Vendor(PreLaunchHook): """Prepend python 2 dependencies for py2 hosts.""" # WARNING This hook will probably be deprecated in OpenPype 3 - kept for test order = 10 - app_groups = ["hiero", "nuke", "nukex"] + app_groups = ["hiero", "nuke", "nukex", "unreal"] def execute(self): # Prepare vendor dir path diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index 55c5b63f60..66102a2ae1 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -51,8 +51,37 @@ def set_start_end_frames(): "name": asset_name }) - bpy.context.scene.frame_start = asset_doc["data"]["frameStart"] - bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"] + scene = bpy.context.scene + + # Default scene settings + frameStart = scene.frame_start + frameEnd = scene.frame_end + fps = scene.render.fps + resolution_x = scene.render.resolution_x + resolution_y = scene.render.resolution_y + + # Check if settings are set + data = asset_doc.get("data") + + if not data: + return + + if data.get("frameStart"): + frameStart = data.get("frameStart") + if data.get("frameEnd"): + frameEnd = data.get("frameEnd") + if data.get("fps"): + fps = data.get("fps") + if data.get("resolutionWidth"): + resolution_x = data.get("resolutionWidth") + if data.get("resolutionHeight"): + resolution_y = data.get("resolutionHeight") + + scene.frame_start = frameStart + scene.frame_end = frameEnd + scene.render.fps = fps + scene.render.resolution_x = resolution_x + scene.render.resolution_y = resolution_y def on_new(arg1, arg2): diff --git a/openpype/hosts/blender/plugins/load/load_layout.py b/openpype/hosts/blender/plugins/load/load_layout.py index 73b12d8c25..f1f2fdcddd 100644 --- a/openpype/hosts/blender/plugins/load/load_layout.py +++ b/openpype/hosts/blender/plugins/load/load_layout.py @@ -292,6 +292,9 @@ class UnrealLayoutLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" + animation_creator_name = "CreateAnimation" + setdress_creator_name = "CreateSetDress" + def _remove_objects(self, objects): for obj in list(objects): if obj.type == 'ARMATURE': @@ -368,7 +371,7 @@ class UnrealLayoutLoader(plugin.AssetLoader): location.get('z') ) obj.rotation_euler = ( - rotation.get('x'), + rotation.get('x') + math.pi / 2, -rotation.get('y'), -rotation.get('z') ) diff --git a/openpype/hosts/blender/plugins/publish/validate_object_mode.py b/openpype/hosts/blender/plugins/publish/validate_object_mode.py new file mode 100644 index 0000000000..1c82628c1c --- /dev/null +++ b/openpype/hosts/blender/plugins/publish/validate_object_mode.py @@ -0,0 +1,35 @@ +from typing import List + +import pyblish.api +import openpype.hosts.blender.api.action + + +class ValidateObjectIsInObjectMode(pyblish.api.InstancePlugin): + """Validate that the current object is in Object Mode.""" + + order = pyblish.api.ValidatorOrder - 0.01 + hosts = ["blender"] + families = ["model", "rig"] + category = "geometry" + label = "Object is in Object Mode" + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] + optional = True + + @classmethod + def get_invalid(cls, instance) -> List: + invalid = [] + for obj in [obj for obj in instance]: + try: + if obj.type == 'MESH' or obj.type == 'ARMATURE': + # Check if the object is in object mode. + if not obj.mode == 'OBJECT': + invalid.append(obj) + except Exception: + continue + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError( + f"Object found in instance is not in Object Mode: {invalid}") diff --git a/openpype/hosts/resolve/__init__.py b/openpype/hosts/resolve/__init__.py index 734e0bc5df..3e49ce3b9b 100644 --- a/openpype/hosts/resolve/__init__.py +++ b/openpype/hosts/resolve/__init__.py @@ -11,7 +11,9 @@ from .api.pipeline import ( update_container, publish, launch_workfiles_app, - maintained_selection + maintained_selection, + remove_instance, + list_instances ) from .api.lib import ( @@ -73,6 +75,8 @@ __all__ = [ "publish", "launch_workfiles_app", "maintained_selection", + "remove_instance", + "list_instances", # utils "setup", diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py index 5ed7aeab34..e7be3fc963 100644 --- a/openpype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -12,7 +12,8 @@ from avalon.tools import ( creator, loader, sceneinventory, - libraryloader + libraryloader, + subsetmanager ) @@ -64,8 +65,9 @@ class OpenPypeMenu(QtWidgets.QWidget): publish_btn = QtWidgets.QPushButton("Publish ...", self) load_btn = QtWidgets.QPushButton("Load ...", self) inventory_btn = QtWidgets.QPushButton("Inventory ...", self) + subsetm_btn = QtWidgets.QPushButton("Subset Manager ...", self) libload_btn = QtWidgets.QPushButton("Library ...", self) - # rename_btn = QtWidgets.QPushButton("Rename ...", self) + # rename_btn = QtWidgets.QPushButton("Rename", self) # set_colorspace_btn = QtWidgets.QPushButton( # "Set colorspace from presets", self # ) @@ -81,6 +83,7 @@ class OpenPypeMenu(QtWidgets.QWidget): layout.addWidget(publish_btn) layout.addWidget(load_btn) layout.addWidget(inventory_btn) + layout.addWidget(subsetm_btn) layout.addWidget(Spacer(15, self)) @@ -102,6 +105,7 @@ class OpenPypeMenu(QtWidgets.QWidget): publish_btn.clicked.connect(self.on_publish_clicked) load_btn.clicked.connect(self.on_load_clicked) inventory_btn.clicked.connect(self.on_inventory_clicked) + subsetm_btn.clicked.connect(self.on_subsetm_clicked) libload_btn.clicked.connect(self.on_libload_clicked) # rename_btn.clicked.connect(self.on_rename_clicked) # set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) @@ -127,6 +131,10 @@ class OpenPypeMenu(QtWidgets.QWidget): print("Clicked Inventory") sceneinventory.show() + def on_subsetm_clicked(self): + print("Clicked Subset Manager") + subsetmanager.show() + def on_libload_clicked(self): print("Clicked Library") libraryloader.show() diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py index e3a832459b..a659ac7e51 100644 --- a/openpype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -258,3 +258,51 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): # Whether instances should be passthrough based on new value timeline_item = instance.data["item"] set_publish_attribute(timeline_item, new_value) + + +def remove_instance(instance): + """Remove instance marker from track item.""" + instance_id = instance.get("uuid") + + selected_timeline_items = lib.get_current_timeline_items( + filter=True, selecting_color=lib.publish_clip_color) + + found_ti = None + for timeline_item_data in selected_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + + # get openpype tag data + tag_data = lib.get_timeline_item_pype_tag(timeline_item) + _ti_id = tag_data.get("uuid") + if _ti_id == instance_id: + found_ti = timeline_item + break + + if found_ti is None: + return + + # removing instance by marker color + print(f"Removing instance: {found_ti.GetName()}") + found_ti.DeleteMarkersByColor(lib.pype_marker_color) + + +def list_instances(): + """List all created instances from current workfile.""" + listed_instances = [] + selected_timeline_items = lib.get_current_timeline_items( + filter=True, selecting_color=lib.publish_clip_color) + + for timeline_item_data in selected_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + ti_name = timeline_item.GetName().split(".")[0] + + # get openpype tag data + tag_data = lib.get_timeline_item_pype_tag(timeline_item) + + if tag_data: + asset = tag_data.get("asset") + subset = tag_data.get("subset") + tag_data["label"] = f"{ti_name} [{asset}-{subset}]" + listed_instances.append(tag_data) + + return listed_instances diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 3833795b96..4712d0a8b9 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -1,4 +1,5 @@ import re +import uuid from avalon import api import openpype.api as pype from openpype.hosts import resolve @@ -697,13 +698,13 @@ class PublishClip: Populating the tag data into internal variable self.tag_data """ # define vertical sync attributes - master_layer = True + hero_track = True self.review_layer = "" if self.vertical_sync: # check if track name is not in driving layer if self.track_name not in self.driving_layer: # if it is not then define vertical sync as None - master_layer = False + hero_track = False # increasing steps by index of rename iteration self.count_steps *= self.rename_index @@ -717,7 +718,7 @@ class PublishClip: self.tag_data[_k] = _v["value"] # driving layer is set as positive match - if master_layer or self.vertical_sync: + if hero_track or self.vertical_sync: # mark review layer if self.review_track and ( self.review_track not in self.review_track_default): @@ -751,35 +752,39 @@ class PublishClip: hierarchy_formating_data ) - tag_hierarchy_data.update({"masterLayer": True}) - if master_layer and self.vertical_sync: - # tag_hierarchy_data.update({"masterLayer": True}) + tag_hierarchy_data.update({"heroTrack": True}) + if hero_track and self.vertical_sync: self.vertical_clip_match.update({ (self.clip_in, self.clip_out): tag_hierarchy_data }) - if not master_layer and self.vertical_sync: + if not hero_track and self.vertical_sync: # driving layer is set as negative match - for (_in, _out), master_data in self.vertical_clip_match.items(): - master_data.update({"masterLayer": False}) + for (_in, _out), hero_data in self.vertical_clip_match.items(): + hero_data.update({"heroTrack": False}) if _in == self.clip_in and _out == self.clip_out: - data_subset = master_data["subset"] - # add track index in case duplicity of names in master data + data_subset = hero_data["subset"] + # add track index in case duplicity of names in hero data if self.subset in data_subset: - master_data["subset"] = self.subset + str( + hero_data["subset"] = self.subset + str( self.track_index) # in case track name and subset name is the same then add if self.subset_name == self.track_name: - master_data["subset"] = self.subset + hero_data["subset"] = self.subset # assing data to return hierarchy data to tag - tag_hierarchy_data = master_data + tag_hierarchy_data = hero_data # add data to return data dict self.tag_data.update(tag_hierarchy_data) - if master_layer and self.review_layer: + # add uuid to tag data + self.tag_data["uuid"] = str(uuid.uuid4()) + + # add review track only to hero track + if hero_track and self.review_layer: self.tag_data.update({"reviewTrack": self.review_layer}) + def _solve_tag_hierarchy_data(self, hierarchy_formating_data): """ Solve tag data from hierarchy data and templates. """ # fill up clip name and hierarchy keys diff --git a/openpype/hosts/resolve/plugins/create/create_shot_clip.py b/openpype/hosts/resolve/plugins/create/create_shot_clip.py index 2916a52298..41fdbf5c61 100644 --- a/openpype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/openpype/hosts/resolve/plugins/create/create_shot_clip.py @@ -117,7 +117,7 @@ class CreateShotClip(resolve.Creator): "vSyncTrack": { "value": gui_tracks, # noqa "type": "QComboBox", - "label": "Master track", + "label": "Hero track", "target": "ui", "toolTip": "Select driving track name which should be mastering all others", # noqa "order": 1} diff --git a/openpype/hosts/resolve/plugins/publish/collect_instances.py b/openpype/hosts/resolve/plugins/publish/precollect_instances.py similarity index 95% rename from openpype/hosts/resolve/plugins/publish/collect_instances.py rename to openpype/hosts/resolve/plugins/publish/precollect_instances.py index f4eeb39754..c38cbc4f73 100644 --- a/openpype/hosts/resolve/plugins/publish/collect_instances.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_instances.py @@ -5,11 +5,11 @@ from openpype.hosts import resolve from pprint import pformat -class CollectInstances(pyblish.api.ContextPlugin): +class PrecollectInstances(pyblish.api.ContextPlugin): """Collect all Track items selection.""" order = pyblish.api.CollectorOrder - 0.59 - label = "Collect Instances" + label = "Precollect Instances" hosts = ["resolve"] def process(self, context): @@ -26,7 +26,7 @@ class CollectInstances(pyblish.api.ContextPlugin): data = dict() timeline_item = timeline_item_data["clip"]["item"] - # get openpype tag data + # get pype tag data tag_data = resolve.get_timeline_item_pype_tag(timeline_item) self.log.debug(f"__ tag_data: {pformat(tag_data)}") @@ -102,10 +102,10 @@ class CollectInstances(pyblish.api.ContextPlugin): }) def create_shot_instance(self, context, timeline_item, **data): - master_layer = data.get("masterLayer") + hero_track = data.get("heroTrack") hierarchy_data = data.get("hierarchyData") - if not master_layer: + if not hero_track: return if not hierarchy_data: diff --git a/openpype/hosts/resolve/plugins/publish/collect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py similarity index 88% rename from openpype/hosts/resolve/plugins/publish/collect_workfile.py rename to openpype/hosts/resolve/plugins/publish/precollect_workfile.py index a66284ed02..ee05fb6f13 100644 --- a/openpype/hosts/resolve/plugins/publish/collect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -9,10 +9,10 @@ from openpype.hosts.resolve.otio import davinci_export reload(davinci_export) -class CollectWorkfile(pyblish.api.ContextPlugin): - """Inject the current working file into context""" +class PrecollectWorkfile(pyblish.api.ContextPlugin): + """Precollect the current working file into context""" - label = "Collect Workfile" + label = "Precollect Workfile" order = pyblish.api.CollectorOrder - 0.6 def process(self, context): @@ -21,8 +21,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin): subset = "workfile" project = resolve.get_current_project() fps = project.GetSetting("timelineFrameRate") - - active_timeline = resolve.get_current_timeline() video_tracks = resolve.get_video_track_names() # adding otio timeline to context diff --git a/openpype/hosts/resolve/utility_scripts/OTIO_export.py b/openpype/hosts/resolve/utility_scripts/OTIO_export.py index 91bc2c5700..0431eb7daa 100644 --- a/openpype/hosts/resolve/utility_scripts/OTIO_export.py +++ b/openpype/hosts/resolve/utility_scripts/OTIO_export.py @@ -58,9 +58,8 @@ def _close_window(event): def _export_button(event): pm = resolve.GetProjectManager() project = pm.GetCurrentProject() - fps = project.GetSetting("timelineFrameRate") timeline = project.GetCurrentTimeline() - otio_timeline = otio_export.create_otio_timeline(timeline, fps) + otio_timeline = otio_export.create_otio_timeline(project) otio_path = os.path.join( itm["exportfilebttn"].Text, timeline.GetName() + ".otio") diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py index 57602d9610..68c142c005 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_instances.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py @@ -18,7 +18,7 @@ class CollectInstances(pyblish.api.ContextPlugin): )) for instance_data in workfile_instances: - instance_data["fps"] = context.data["fps"] + instance_data["fps"] = context.data["sceneFps"] # Store workfile instance data to instance data instance_data["originData"] = copy.deepcopy(instance_data) @@ -32,6 +32,11 @@ class CollectInstances(pyblish.api.ContextPlugin): subset_name = instance_data["subset"] name = instance_data.get("name", subset_name) instance_data["name"] = name + instance_data["label"] = "{} [{}-{}]".format( + name, + context.data["sceneFrameStart"], + context.data["sceneFrameEnd"] + ) active = instance_data.get("active", True) instance_data["active"] = active @@ -73,8 +78,8 @@ class CollectInstances(pyblish.api.ContextPlugin): if instance is None: continue - instance.data["frameStart"] = context.data["frameStart"] - instance.data["frameEnd"] = context.data["frameEnd"] + instance.data["frameStart"] = context.data["sceneFrameStart"] + instance.data["frameEnd"] = context.data["sceneFrameEnd"] self.log.debug("Created instance: {}\n{}".format( instance, json.dumps(instance.data, indent=4) diff --git a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py index 7965112136..e683c66ea9 100644 --- a/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py +++ b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py @@ -127,11 +127,11 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): "currentFile": workfile_path, "sceneWidth": width, "sceneHeight": height, - "pixelAspect": pixel_apsect, - "frameStart": frame_start, - "frameEnd": frame_end, - "fps": frame_rate, - "fieldOrder": field_order + "scenePixelAspect": pixel_apsect, + "sceneFrameStart": frame_start, + "sceneFrameEnd": frame_end, + "sceneFps": frame_rate, + "sceneFieldOrder": field_order } self.log.debug( "Scene data: {}".format(json.dumps(scene_data, indent=4)) diff --git a/openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py b/openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py new file mode 100644 index 0000000000..fead3393ae --- /dev/null +++ b/openpype/hosts/tvpaint/plugins/publish/validate_project_settings.py @@ -0,0 +1,36 @@ +import json + +import pyblish.api + + +class ValidateProjectSettings(pyblish.api.ContextPlugin): + """Validate project settings against database. + """ + + label = "Validate Project Settings" + order = pyblish.api.ValidatorOrder + optional = True + + def process(self, context): + scene_data = { + "frameStart": context.data.get("sceneFrameStart"), + "frameEnd": context.data.get("sceneFrameEnd"), + "fps": context.data.get("sceneFps"), + "resolutionWidth": context.data.get("sceneWidth"), + "resolutionHeight": context.data.get("sceneHeight"), + "pixelAspect": context.data.get("scenePixelAspect") + } + invalid = {} + for k in scene_data.keys(): + expected_value = context.data["assetEntity"]["data"][k] + if scene_data[k] != expected_value: + invalid[k] = { + "current": scene_data[k], "expected": expected_value + } + + if invalid: + raise AssertionError( + "Project settings does not match database:\n{}".format( + json.dumps(invalid, sort_keys=True, indent=4) + ) + ) diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 5945d0486b..c698be63de 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -23,8 +23,8 @@ class UnrealPrelaunchHook(PreLaunchHook): def execute(self): asset_name = self.data["asset_name"] task_name = self.data["task_name"] - workdir = self.env["AVALON_WORKDIR"] - engine_version = self.app_name.split("_")[-1] + workdir = self.launch_context.env["AVALON_WORKDIR"] + engine_version = self.app_name.split("_")[-1].replace("-", ".") unreal_project_name = f"{asset_name}_{task_name}" # Unreal is sensitive about project names longer then 20 chars @@ -81,8 +81,8 @@ class UnrealPrelaunchHook(PreLaunchHook): # Set "AVALON_UNREAL_PLUGIN" to current process environment for # execution of `create_unreal_project` env_key = "AVALON_UNREAL_PLUGIN" - if self.env.get(env_key): - os.environ[env_key] = self.env[env_key] + if self.launch_context.env.get(env_key): + os.environ[env_key] = self.launch_context.env[env_key] unreal_lib.create_unreal_project( unreal_project_name, diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 554c0d8ec3..ce8f8ec2b6 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -104,7 +104,8 @@ from .plugin_tools import ( from .local_settings import ( IniSettingRegistry, JSONSettingRegistry, - PypeSettingsRegistry, + OpenPypeSecureRegistry, + OpenPypeSettingsRegistry, get_local_site_id, change_openpype_mongo_url ) @@ -217,7 +218,8 @@ __all__ = [ "IniSettingRegistry", "JSONSettingRegistry", - "PypeSettingsRegistry", + "OpenPypeSecureRegistry", + "OpenPypeSettingsRegistry", "get_local_site_id", "change_openpype_mongo_url", diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 1f7c693b85..2d8726352a 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1123,6 +1123,7 @@ class BuildWorkfile: return output +@with_avalon def get_creator_by_name(creator_name, case_sensitive=False): """Find creator plugin by name. diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 82507cb0c0..56bdd047c9 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -5,6 +5,7 @@ from datetime import datetime from abc import ABCMeta, abstractmethod import json +# TODO Use pype igniter logic instead of using duplicated code # disable lru cache in Python 2 try: from functools import lru_cache @@ -25,11 +26,115 @@ except ImportError: import platform -import appdirs import six +import appdirs from .import validate_mongo_connection +_PLACEHOLDER = object() + + +class OpenPypeSecureRegistry: + """Store information using keyring. + + Registry should be used for private data that should be available only for + user. + + All passed registry names will have added prefix `OpenPype/` to easier + identify which data were created by OpenPype. + + Args: + name(str): Name of registry used as identifier for data. + """ + def __init__(self, name): + try: + import keyring + + except Exception: + raise NotImplementedError( + "Python module `keyring` is not available." + ) + + # hack for cx_freeze and Windows keyring backend + if platform.system().lower() == "windows": + from keyring.backends import Windows + + keyring.set_keyring(Windows.WinVaultKeyring()) + + # Force "OpenPype" prefix + self._name = "/".join(("OpenPype", name)) + + def set_item(self, name, value): + # type: (str, str) -> None + """Set sensitive item into system's keyring. + + This uses `Keyring module`_ to save sensitive stuff into system's + keyring. + + Args: + name (str): Name of the item. + value (str): Value of the item. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + keyring.set_password(self._name, name, value) + + @lru_cache(maxsize=32) + def get_item(self, name, default=_PLACEHOLDER): + """Get value of sensitive item from system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item. + default (Any): Default value if item is not available. + + Returns: + value (str): Value of the item. + + Raises: + ValueError: If item doesn't exist and default is not defined. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + value = keyring.get_password(self._name, name) + if value is not None: + return value + + if default is not _PLACEHOLDER: + return default + + # NOTE Should raise `KeyError` + raise ValueError( + "Item {}:{} does not exist in keyring.".format(self._name, name) + ) + + def delete_item(self, name): + # type: (str) -> None + """Delete value stored in system's keyring. + + See also `Keyring module`_ + + Args: + name (str): Name of the item to be deleted. + + .. _Keyring module: + https://github.com/jaraco/keyring + + """ + import keyring + + self.get_item.cache_clear() + keyring.delete_password(self._name, name) + @six.add_metaclass(ABCMeta) class ASettingRegistry(): @@ -48,13 +153,6 @@ class ASettingRegistry(): # type: (str) -> ASettingRegistry super(ASettingRegistry, self).__init__() - if six.PY3: - import keyring - # hack for cx_freeze and Windows keyring backend - if platform.system() == "Windows": - from keyring.backends import Windows - keyring.set_keyring(Windows.WinVaultKeyring()) - self._name = name self._items = {} @@ -120,7 +218,7 @@ class ASettingRegistry(): """Delete item from settings. Note: - see :meth:`pype.lib.local_settings.ARegistrySettings.delete_item` + see :meth:`openpype.lib.user_settings.ARegistrySettings.delete_item` """ pass @@ -129,78 +227,6 @@ class ASettingRegistry(): del self._items[name] self._delete_item(name) - def set_secure_item(self, name, value): - # type: (str, str) -> None - """Set sensitive item into system's keyring. - - This uses `Keyring module`_ to save sensitive stuff into system's - keyring. - - Args: - name (str): Name of the item. - value (str): Value of the item. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - keyring.set_password(self._name, name, value) - - @lru_cache(maxsize=32) - def get_secure_item(self, name): - # type: (str) -> str - """Get value of sensitive item from system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item. - - Returns: - value (str): Value of the item. - - Raises: - ValueError: If item doesn't exist. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - value = keyring.get_password(self._name, name) - if not value: - raise ValueError( - "Item {}:{} does not exist in keyring.".format( - self._name, name)) - return value - - def delete_secure_item(self, name): - # type: (str) -> None - """Delete value stored in system's keyring. - - See also `Keyring module`_ - - Args: - name (str): Name of the item to be deleted. - - .. _Keyring module: - https://github.com/jaraco/keyring - - """ - if six.PY2: - raise NotImplementedError( - "Keyring not available on Python 2 hosts") - import keyring - self.get_secure_item.cache_clear() - keyring.delete_password(self._name, name) - class IniSettingRegistry(ASettingRegistry): """Class using :mod:`configparser`. @@ -218,7 +244,7 @@ class IniSettingRegistry(ASettingRegistry): if not os.path.exists(self._registry_file): with open(self._registry_file, mode="w") as cfg: print("# Settings registry", cfg) - print("# Generated by Pype {}".format(version), cfg) + print("# Generated by OpenPype {}".format(version), cfg) now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") print("# {}".format(now), cfg) @@ -352,7 +378,7 @@ class IniSettingRegistry(ASettingRegistry): """Delete item from default section. Note: - See :meth:`~pype.lib.IniSettingsRegistry.delete_item_from_section` + See :meth:`~openpype.lib.IniSettingsRegistry.delete_item_from_section` """ self.delete_item_from_section("MAIN", name) @@ -369,7 +395,7 @@ class JSONSettingRegistry(ASettingRegistry): now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") header = { "__metadata__": { - "pype-version": os.getenv("OPENPYPE_VERSION", "N/A"), + "openpype-version": os.getenv("OPENPYPE_VERSION", "N/A"), "generated": now }, "registry": {} @@ -387,7 +413,7 @@ class JSONSettingRegistry(ASettingRegistry): """Get item value from registry json. Note: - See :meth:`pype.lib.JSONSettingRegistry.get_item` + See :meth:`openpype.lib.JSONSettingRegistry.get_item` """ with open(self._registry_file, mode="r") as cfg: @@ -420,7 +446,7 @@ class JSONSettingRegistry(ASettingRegistry): """Set item value to registry json. Note: - See :meth:`pype.lib.JSONSettingRegistry.set_item` + See :meth:`openpype.lib.JSONSettingRegistry.set_item` """ with open(self._registry_file, "r+") as cfg: @@ -452,8 +478,8 @@ class JSONSettingRegistry(ASettingRegistry): json.dump(data, cfg, indent=4) -class PypeSettingsRegistry(JSONSettingRegistry): - """Class handling Pype general settings registry. +class OpenPypeSettingsRegistry(JSONSettingRegistry): + """Class handling OpenPype general settings registry. Attributes: vendor (str): Name used for path construction. @@ -461,21 +487,23 @@ class PypeSettingsRegistry(JSONSettingRegistry): """ - def __init__(self): + def __init__(self, name=None): self.vendor = "pypeclub" - self.product = "pype" + self.product = "openpype" + if not name: + name = "openpype_settings" path = appdirs.user_data_dir(self.product, self.vendor) - super(PypeSettingsRegistry, self).__init__("pype_settings", path) + super(OpenPypeSettingsRegistry, self).__init__(name, path) def _create_local_site_id(registry=None): """Create a local site identifier.""" - from uuid import uuid4 + from coolname import generate_slug if registry is None: - registry = PypeSettingsRegistry() + registry = OpenPypeSettingsRegistry() - new_id = str(uuid4()) + new_id = generate_slug(3) print("Created local site id \"{}\"".format(new_id)) @@ -489,7 +517,7 @@ def get_local_site_id(): Identifier is created if does not exists yet. """ - registry = PypeSettingsRegistry() + registry = OpenPypeSettingsRegistry() try: return registry.get_item("localId") except ValueError: @@ -504,5 +532,9 @@ def change_openpype_mongo_url(new_mongo_url): """ validate_mongo_connection(new_mongo_url) - registry = PypeSettingsRegistry() - registry.set_secure_item("pypeMongo", new_mongo_url) + key = "openPypeMongo" + registry = OpenPypeSecureRegistry("mongodb") + existing_value = registry.get_item(key, None) + if existing_value is not None: + registry.delete_item(key) + registry.set_item(key, new_mongo_url) diff --git a/openpype/modules/clockify/clockify_api.py b/openpype/modules/clockify/clockify_api.py index d88b2ef8df..29de5de0c9 100644 --- a/openpype/modules/clockify/clockify_api.py +++ b/openpype/modules/clockify/clockify_api.py @@ -1,13 +1,16 @@ import os import re import time -import requests import json import datetime +import requests from .constants import ( - CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH + CLOCKIFY_ENDPOINT, + ADMIN_PERMISSION_NAMES ) +from openpype.lib.local_settings import OpenPypeSecureRegistry + def time_check(obj): if obj.request_counter < 10: @@ -31,6 +34,8 @@ class ClockifyAPI: self.request_counter = 0 self.request_time = time.time() + self.secure_registry = OpenPypeSecureRegistry("clockify") + @property def headers(self): return {"X-Api-Key": self.api_key} @@ -129,22 +134,10 @@ class ClockifyAPI: return False def get_api_key(self): - api_key = None - try: - file = open(CREDENTIALS_JSON_PATH, 'r') - api_key = json.load(file).get('api_key', None) - if api_key == '': - api_key = None - except Exception: - file = open(CREDENTIALS_JSON_PATH, 'w') - file.close() - return api_key + return self.secure_registry.get_item("api_key", None) def save_api_key(self, api_key): - data = {'api_key': api_key} - file = open(CREDENTIALS_JSON_PATH, 'w') - file.write(json.dumps(data)) - file.close() + self.secure_registry.set_item("api_key", api_key) def get_workspaces(self): action_url = 'workspaces/' diff --git a/openpype/modules/clockify/constants.py b/openpype/modules/clockify/constants.py index 38ad4b64cf..66f6cb899a 100644 --- a/openpype/modules/clockify/constants.py +++ b/openpype/modules/clockify/constants.py @@ -1,17 +1,12 @@ import os -import appdirs CLOCKIFY_FTRACK_SERVER_PATH = os.path.join( - os.path.dirname(__file__), "ftrack", "server" + os.path.dirname(os.path.abspath(__file__)), "ftrack", "server" ) CLOCKIFY_FTRACK_USER_PATH = os.path.join( - os.path.dirname(__file__), "ftrack", "user" + os.path.dirname(os.path.abspath(__file__)), "ftrack", "user" ) -CREDENTIALS_JSON_PATH = os.path.normpath(os.path.join( - appdirs.user_data_dir("pype-app", "pype"), - "clockify.json" -)) ADMIN_PERMISSION_NAMES = ["WORKSPACE_OWN", "WORKSPACE_ADMIN"] CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/" diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index e1af9c15a7..cd383cbdc6 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -210,3 +210,7 @@ class FtrackModule( def tray_exit(self): return self.tray_module.stop_action_server() + + def set_credentials_to_env(self, username, api_key): + os.environ["FTRACK_API_USER"] = username or "" + os.environ["FTRACK_API_KEY"] = api_key or "" diff --git a/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py index 7826d833ac..f14857bc98 100644 --- a/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py +++ b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py @@ -9,7 +9,7 @@ class PrePython2Support(PreLaunchHook): Path to vendor modules is added to the beggining of PYTHONPATH. """ # There will be needed more granular filtering in future - app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio"] + app_groups = ["maya", "nuke", "nukex", "hiero", "nukestudio", "unreal"] def execute(self): # Prepare vendor dir path diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 7511c2627b..79e1366a0d 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -891,6 +891,33 @@ class SyncEntitiesFactory: self.entities_dict[parent_id]["children"].remove(id) + def _query_custom_attributes(self, session, conf_ids, entity_ids): + output = [] + # Prepare values to query + attributes_joined = join_query_keys(conf_ids) + attributes_len = len(conf_ids) + chunk_size = int(5000 / attributes_len) + for idx in range(0, len(entity_ids), chunk_size): + entity_ids_joined = join_query_keys( + entity_ids[idx:idx + chunk_size] + ) + + call_expr = [{ + "action": "query", + "expression": ( + "select value, entity_id from ContextCustomAttributeValue " + "where entity_id in ({}) and configuration_id in ({})" + ).format(entity_ids_joined, attributes_joined) + }] + if hasattr(session, "call"): + [result] = session.call(call_expr) + else: + [result] = session._call(call_expr) + + for item in result["data"]: + output.append(item) + return output + def set_cutom_attributes(self): self.log.debug("* Preparing custom attributes") # Get custom attributes and values @@ -1000,31 +1027,13 @@ class SyncEntitiesFactory: copy.deepcopy(prepared_avalon_attr_ca_id) ) - # TODO query custom attributes by entity_id - entity_ids_joined = ", ".join([ - "\"{}\"".format(id) for id in sync_ids - ]) - attributes_joined = ", ".join([ - "\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys() - ]) - - cust_attr_query = ( - "select value, configuration_id, entity_id" - " from ContextCustomAttributeValue" - " where entity_id in ({}) and configuration_id in ({})" + items = self._query_custom_attributes( + self.session, + list(attribute_key_by_id.keys()), + sync_ids ) - call_expr = [{ - "action": "query", - "expression": cust_attr_query.format( - entity_ids_joined, attributes_joined - ) - }] - if hasattr(self.session, "call"): - [values] = self.session.call(call_expr) - else: - [values] = self.session._call(call_expr) - for item in values["data"]: + for item in items: entity_id = item["entity_id"] attr_id = item["configuration_id"] key = attribute_key_by_id[attr_id] @@ -1106,28 +1115,14 @@ class SyncEntitiesFactory: for key, val in prepare_dict_avalon.items(): entity_dict["avalon_attrs"][key] = val - # Prepare values to query - entity_ids_joined = ", ".join([ - "\"{}\"".format(id) for id in sync_ids - ]) - attributes_joined = ", ".join([ - "\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys() - ]) - avalon_hier = [] - call_expr = [{ - "action": "query", - "expression": ( - "select value, entity_id, configuration_id" - " from ContextCustomAttributeValue" - " where entity_id in ({}) and configuration_id in ({})" - ).format(entity_ids_joined, attributes_joined) - }] - if hasattr(self.session, "call"): - [values] = self.session.call(call_expr) - else: - [values] = self.session._call(call_expr) + items = self._query_custom_attributes( + self.session, + list(attribute_key_by_id.keys()), + sync_ids + ) - for item in values["data"]: + avalon_hier = [] + for item in items: value = item["value"] # WARNING It is not possible to propage enumerate hierachical # attributes with multiselection 100% right. Unseting all values @@ -1256,19 +1251,21 @@ class SyncEntitiesFactory: if not msg or not items: continue self.report_items["warning"][msg] = items - tasks = {} - for task_type in task_types: - task_type_name = task_type["name"] - # Set short name to empty string - # QUESTION Maybe better would be to lower and remove spaces - # from task type name. - tasks[task_type_name] = { - "short_name": "" - } current_project_anatomy_data = get_anatomy_settings( project_name, exclude_locals=True ) + anatomy_tasks = current_project_anatomy_data["tasks"] + tasks = {} + default_type_data = { + "short_name": "" + } + for task_type in task_types: + task_type_name = task_type["name"] + tasks[task_type_name] = copy.deepcopy( + anatomy_tasks.get(task_type_name) + or default_type_data + ) project_config = { "tasks": tasks, diff --git a/openpype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py index 16b1fb25fb..2d719347e7 100644 --- a/openpype/modules/ftrack/lib/credentials.py +++ b/openpype/modules/ftrack/lib/credentials.py @@ -1,23 +1,16 @@ import os -import json import ftrack_api -import appdirs -import getpass + try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse -CONFIG_PATH = os.path.normpath(appdirs.user_data_dir("pype-app", "pype")) -CREDENTIALS_FILE_NAME = "ftrack_cred.json" -CREDENTIALS_PATH = os.path.join(CONFIG_PATH, CREDENTIALS_FILE_NAME) -CREDENTIALS_FOLDER = os.path.dirname(CREDENTIALS_PATH) +from openpype.lib import OpenPypeSecureRegistry -if not os.path.isdir(CREDENTIALS_FOLDER): - os.makedirs(CREDENTIALS_FOLDER) - -USER_GETTER = None +USERNAME_KEY = "username" +API_KEY_KEY = "api_key" def get_ftrack_hostname(ftrack_server=None): @@ -30,112 +23,73 @@ def get_ftrack_hostname(ftrack_server=None): return urlparse(ftrack_server).hostname -def get_user(): - if USER_GETTER: - return USER_GETTER() - return getpass.getuser() +def _get_ftrack_secure_key(hostname, key): + """Secure item key for entered hostname.""" + return "/".join(("ftrack", hostname, key)) -def get_credentials(ftrack_server=None, user=None): - credentials = {} - if not os.path.exists(CREDENTIALS_PATH): - with open(CREDENTIALS_PATH, "w") as file: - file.write(json.dumps(credentials)) - file.close() - return credentials - - with open(CREDENTIALS_PATH, "r") as file: - content = file.read() - +def get_credentials(ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - if not user: - user = get_user() + username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY) + api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY) - content_json = json.loads(content or "{}") - credentials = content_json.get(hostname, {}).get(user) or {} + username_registry = OpenPypeSecureRegistry(username_name) + api_key_registry = OpenPypeSecureRegistry(api_key_name) - return credentials - - -def save_credentials(ft_user, ft_api_key, ftrack_server=None, user=None): - hostname = get_ftrack_hostname(ftrack_server) - if not user: - user = get_user() - - with open(CREDENTIALS_PATH, "r") as file: - content = file.read() - - content_json = json.loads(content or "{}") - if hostname not in content_json: - content_json[hostname] = {} - - content_json[hostname][user] = { - "username": ft_user, - "api_key": ft_api_key + return { + USERNAME_KEY: username_registry.get_item(USERNAME_KEY, None), + API_KEY_KEY: api_key_registry.get_item(API_KEY_KEY, None) } - # Deprecated keys - if "username" in content_json: - content_json.pop("username") - if "apiKey" in content_json: - content_json.pop("apiKey") - - with open(CREDENTIALS_PATH, "w") as file: - file.write(json.dumps(content_json, indent=4)) - - -def clear_credentials(ft_user=None, ftrack_server=None, user=None): - if not ft_user: - ft_user = os.environ.get("FTRACK_API_USER") - - if not ft_user: - return +def save_credentials(username, api_key, ftrack_server=None): hostname = get_ftrack_hostname(ftrack_server) - if not user: - user = get_user() + username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY) + api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY) - with open(CREDENTIALS_PATH, "r") as file: - content = file.read() + # Clear credentials + clear_credentials(ftrack_server) - content_json = json.loads(content or "{}") - if hostname not in content_json: - content_json[hostname] = {} + username_registry = OpenPypeSecureRegistry(username_name) + api_key_registry = OpenPypeSecureRegistry(api_key_name) - content_json[hostname].pop(user, None) - - with open(CREDENTIALS_PATH, "w") as file: - file.write(json.dumps(content_json)) + username_registry.set_item(USERNAME_KEY, username) + api_key_registry.set_item(API_KEY_KEY, api_key) -def set_env(ft_user=None, ft_api_key=None): - os.environ["FTRACK_API_USER"] = ft_user or "" - os.environ["FTRACK_API_KEY"] = ft_api_key or "" +def clear_credentials(ftrack_server=None): + hostname = get_ftrack_hostname(ftrack_server) + username_name = _get_ftrack_secure_key(hostname, USERNAME_KEY) + api_key_name = _get_ftrack_secure_key(hostname, API_KEY_KEY) + + username_registry = OpenPypeSecureRegistry(username_name) + api_key_registry = OpenPypeSecureRegistry(api_key_name) + + current_username = username_registry.get_item(USERNAME_KEY, None) + current_api_key = api_key_registry.get_item(API_KEY_KEY, None) + + if current_username is not None: + username_registry.delete_item(USERNAME_KEY) + + if current_api_key is not None: + api_key_registry.delete_item(API_KEY_KEY) -def get_env_credentials(): - return ( - os.environ.get("FTRACK_API_USER"), - os.environ.get("FTRACK_API_KEY") - ) - - -def check_credentials(ft_user, ft_api_key, ftrack_server=None): +def check_credentials(username, api_key, ftrack_server=None): if not ftrack_server: ftrack_server = os.environ["FTRACK_SERVER"] - if not ft_user or not ft_api_key: + if not username or not api_key: return False try: session = ftrack_api.Session( server_url=ftrack_server, - api_key=ft_api_key, - api_user=ft_user + api_key=api_key, + api_user=username ) session.close() except Exception: return False - return True diff --git a/openpype/modules/ftrack/lib/settings.py b/openpype/modules/ftrack/lib/settings.py index f6967411db..027356edc6 100644 --- a/openpype/modules/ftrack/lib/settings.py +++ b/openpype/modules/ftrack/lib/settings.py @@ -1,6 +1,7 @@ import os from openpype.api import get_system_settings + def get_ftrack_settings(): return get_system_settings()["modules"]["ftrack"] @@ -10,7 +11,6 @@ def get_ftrack_url_from_settings(): def get_ftrack_event_mongo_info(): - ftrack_settings = get_ftrack_settings() database_name = os.environ["OPENPYPE_DATABASE_NAME"] collection_name = "ftrack_events" return database_name, collection_name diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 9da5db835b..ee27d8b730 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -30,7 +30,7 @@ class FtrackTrayWrapper: self.bool_action_thread_running = False self.bool_timer_event = False - self.widget_login = login_dialog.CredentialsDialog() + self.widget_login = login_dialog.CredentialsDialog(module) self.widget_login.login_changed.connect(self.on_login_change) self.widget_login.logout_signal.connect(self.on_logout) @@ -56,7 +56,7 @@ class FtrackTrayWrapper: validation = credentials.check_credentials(ft_user, ft_api_key) if validation: self.widget_login.set_credentials(ft_user, ft_api_key) - credentials.set_env(ft_user, ft_api_key) + self.module.set_credentials_to_env(ft_user, ft_api_key) log.info("Connected to Ftrack successfully") self.on_login_change() @@ -337,7 +337,7 @@ class FtrackTrayWrapper: def changed_user(self): self.stop_action_server() - credentials.set_env() + self.module.set_credentials_to_env(None, None) self.validate() def start_timer_manager(self, data): diff --git a/openpype/modules/ftrack/tray/login_dialog.py b/openpype/modules/ftrack/tray/login_dialog.py index ca409ebcaa..ce91c6d012 100644 --- a/openpype/modules/ftrack/tray/login_dialog.py +++ b/openpype/modules/ftrack/tray/login_dialog.py @@ -14,11 +14,13 @@ class CredentialsDialog(QtWidgets.QDialog): login_changed = QtCore.Signal() logout_signal = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, module, parent=None): super(CredentialsDialog, self).__init__(parent) self.setWindowTitle("OpenPype - Ftrack Login") + self._module = module + self._login_server_thread = None self._is_logged = False self._in_advance_mode = False @@ -268,7 +270,7 @@ class CredentialsDialog(QtWidgets.QDialog): verification = credentials.check_credentials(username, api_key) if verification: credentials.save_credentials(username, api_key, False) - credentials.set_env(username, api_key) + self._module.set_credentials_to_env(username, api_key) self.set_credentials(username, api_key) self.login_changed.emit() return verification diff --git a/openpype/modules/idle_manager/idle_module.py b/openpype/modules/idle_manager/idle_module.py index ddccf07f6a..c06dbed78c 100644 --- a/openpype/modules/idle_manager/idle_module.py +++ b/openpype/modules/idle_manager/idle_module.py @@ -40,8 +40,7 @@ class IdleManager(PypeModule, ITrayService): name = "idle_manager" def initialize(self, module_settings): - idle_man_settings = module_settings[self.name] - self.enabled = idle_man_settings["enabled"] + self.enabled = True self.time_callbacks = collections.defaultdict(list) self.idle_thread = None @@ -50,7 +49,8 @@ class IdleManager(PypeModule, ITrayService): return def tray_start(self): - self.start_thread() + if self.time_callbacks: + self.start_thread() def tray_exit(self): self.stop_thread() diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index a8ea5799e6..92edd5aeaa 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -45,11 +45,13 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes): timers_settings = modules_settings[self.name] self.enabled = timers_settings["enabled"] + auto_stop = timers_settings["auto_stop"] # When timer will stop if idle manager is running (minutes) full_time = int(timers_settings["full_time"] * 60) # How many minutes before the timer is stopped will popup the message message_time = int(timers_settings["message_time"] * 60) + self.auto_stop = auto_stop self.time_show_message = full_time - message_time self.time_stop_timer = full_time @@ -160,6 +162,9 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes): def callbacks_by_idle_time(self): """Implementation of IIdleManager interface.""" # Time when message is shown + if not self.auto_stop: + return {} + callbacks = collections.defaultdict(list) callbacks[self.time_show_message].append(lambda: self.time_callback(0)) diff --git a/openpype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py index 5c5dbf018c..390ce443b6 100644 --- a/openpype/plugins/publish/collect_hierarchy.py +++ b/openpype/plugins/publish/collect_hierarchy.py @@ -40,7 +40,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): continue # exclude if not masterLayer True - if not instance.data.get("masterLayer"): + if not instance.data.get("heroTrack"): continue # get asset build data if any available @@ -50,7 +50,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): # suppose that all instances are Shots shot_data['entity_type'] = 'Shot' - shot_data['tasks'] = instance.data.get("tasks") or [] + shot_data['tasks'] = instance.data.get("tasks") or {} shot_data["comments"] = instance.data.get("comments", []) shot_data['custom_attributes'] = { diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json new file mode 100644 index 0000000000..d4130c88be --- /dev/null +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -0,0 +1,10 @@ +{ + "publish": { + "ValidateMissingLayers": { + "enabled": true, + "optional": true, + "active": true + } + }, + "filters": {} +} \ No newline at end of file diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 00e98aa8de..b3065058a1 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -126,6 +126,7 @@ }, "timers_manager": { "enabled": true, + "auto_stop": true, "full_time": 15.0, "message_time": 0.5 }, @@ -165,8 +166,5 @@ }, "standalonepublish_tool": { "enabled": true - }, - "idle_manager": { - "enabled": true } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json index 565500edd2..6bc158aa60 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_main.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_main.json @@ -82,6 +82,10 @@ "type": "schema", "name": "schema_project_harmony" }, + { + "type": "schema", + "name": "schema_project_tvpaint" + }, { "type": "schema", "name": "schema_project_celaction" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json new file mode 100644 index 0000000000..b9fe26a57c --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -0,0 +1,32 @@ +{ + "type": "dict", + "collapsible": true, + "key": "tvpaint", + "label": "TVPaint", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateMissingLayers", + "label": "ValidateMissingLayers" + } + ] + } + ] + }, + { + "type": "schema", + "name": "schema_publish_gui_filter" + } + ] +} diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 8bfb0e90dc..8512514ff3 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -42,6 +42,11 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "boolean", + "key": "auto_stop", + "label": "Auto stop timer" + }, { "type": "number", "decimal": 2, @@ -176,20 +181,6 @@ "label": "Enabled" } ] - }, - { - "type": "dict", - "key": "idle_manager", - "label": "Idle Manager", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] } ] } diff --git a/openpype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py index 7732157122..e820d8ab8b 100644 --- a/openpype/tools/settings/local_settings/general_widget.py +++ b/openpype/tools/settings/local_settings/general_widget.py @@ -5,28 +5,16 @@ class LocalGeneralWidgets(QtWidgets.QWidget): def __init__(self, parent): super(LocalGeneralWidgets, self).__init__(parent) - local_site_name_input = QtWidgets.QLineEdit(self) - - layout = QtWidgets.QFormLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - - layout.addRow("Local site label", local_site_name_input) - - self.local_site_name_input = local_site_name_input def update_local_settings(self, value): - site_label = "" - if value: - site_label = value.get("site_label", site_label) - self.local_site_name_input.setText(site_label) + return + + # RETURNING EARLY TO HIDE WIDGET WITHOUT CONTENT def settings_value(self): # Add changed # If these have changed then output = {} - local_site_name = self.local_site_name_input.text() - if local_site_name: - output["site_label"] = local_site_name - # Do not return output yet since we don't have mechanism to save or - # load these data through api calls + # TEMPORARILY EMPTY AS THERE IS NOTHING TO PUT HERE + return output diff --git a/openpype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py index b6ca56d348..a12a2289b5 100644 --- a/openpype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -80,6 +80,7 @@ class LocalSettingsWidget(QtWidgets.QWidget): general_widget = LocalGeneralWidgets(general_content) general_layout.addWidget(general_widget) + general_expand_widget.hide() self.main_layout.addWidget(general_expand_widget) @@ -126,9 +127,9 @@ class LocalSettingsWidget(QtWidgets.QWidget): self.system_settings.reset() self.project_settings.reset() - self.general_widget.update_local_settings( - value.get(LOCAL_GENERAL_KEY) - ) + # self.general_widget.update_local_settings( + # value.get(LOCAL_GENERAL_KEY) + # ) self.app_widget.update_local_settings( value.get(LOCAL_APPS_KEY) ) @@ -138,9 +139,9 @@ class LocalSettingsWidget(QtWidgets.QWidget): def settings_value(self): output = {} - general_value = self.general_widget.settings_value() - if general_value: - output[LOCAL_GENERAL_KEY] = general_value + # general_value = self.general_widget.settings_value() + # if general_value: + # output[LOCAL_GENERAL_KEY] = general_value app_value = self.app_widget.settings_value() if app_value: diff --git a/openpype/tools/tray/pype_info_widget.py b/openpype/tools/tray/pype_info_widget.py index dbff36eca7..a70a360378 100644 --- a/openpype/tools/tray/pype_info_widget.py +++ b/openpype/tools/tray/pype_info_widget.py @@ -363,7 +363,7 @@ class PypeInfoWidget(QtWidgets.QWidget): "version_value": "OpenPype version:", "executable": "OpenPype executable:", "pype_root": "OpenPype location:", - "mongo_url": "OpenPype Mongo URL:" + "mongo_url": "OpenPype Mongo URL:" } # Prepare keys order keys_order = ["version_value", "executable", "pype_root", "mongo_url"] diff --git a/poetry.lock b/poetry.lock index 6695a7bcca..767aeee500 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,7 +80,7 @@ python-dateutil = ">=2.7.0" [[package]] name = "astroid" -version = "2.5.1" +version = "2.5.2" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -123,14 +123,14 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> [[package]] name = "autopep8" -version = "1.5.5" +version = "1.5.6" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" category = "dev" optional = false python-versions = "*" [package.dependencies] -pycodestyle = ">=2.6.0" +pycodestyle = ">=2.7.0" toml = "*" [[package]] @@ -232,6 +232,14 @@ python-versions = "*" [package.extras] test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] +[[package]] +name = "coolname" +version = "1.1.0" +description = "Random name and slug generator" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "coverage" version = "5.5" @@ -245,7 +253,7 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "3.4.6" +version = "3.4.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false @@ -290,7 +298,7 @@ trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] [[package]] name = "docutils" -version = "0.16" +version = "0.17" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false @@ -306,17 +314,17 @@ python-versions = "*" [[package]] name = "flake8" -version = "3.8.4" +version = "3.9.0" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "ftrack-python-api" @@ -346,7 +354,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "google-api-core" -version = "1.26.1" +version = "1.26.3" description = "Google API client core library" category = "main" optional = false @@ -384,7 +392,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "1.27.1" +version = "1.28.0" description = "Google Authentication Library" category = "main" optional = false @@ -429,7 +437,7 @@ grpc = ["grpcio (>=1.0.0)"] [[package]] name = "httplib2" -version = "0.19.0" +version = "0.19.1" description = "A comprehensive HTTP client library." category = "main" optional = false @@ -456,7 +464,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "3.7.2" +version = "3.10.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -468,7 +476,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" @@ -480,7 +488,7 @@ python-versions = "*" [[package]] name = "isort" -version = "5.7.0" +version = "5.8.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false @@ -579,11 +587,11 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [[package]] name = "lazy-object-proxy" -version = "1.5.2" +version = "1.6.0" description = "A fast and thorough lazy object proxy." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "log4mongo" @@ -653,7 +661,7 @@ pyparsing = ">=2.0.2" [[package]] name = "parso" -version = "0.8.1" +version = "0.8.2" description = "A Python Parser" category = "dev" optional = false @@ -676,7 +684,7 @@ six = "*" [[package]] name = "pillow" -version = "8.1.2" +version = "8.2.0" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -698,7 +706,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "protobuf" -version = "3.15.6" +version = "3.15.7" description = "Protocol Buffers" category = "main" optional = false @@ -752,7 +760,7 @@ python-versions = "*" [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false @@ -780,7 +788,7 @@ snowballstemmer = "*" [[package]] name = "pyflakes" -version = "2.2.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -796,14 +804,14 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.7.2" +version = "2.7.4" description = "python code static checker" category = "dev" optional = false python-versions = "~=3.6" [package.dependencies] -astroid = ">=2.5.1,<2.6" +astroid = ">=2.5.2,<2.7" colorama = {version = "*", markers = "sys_platform == \"win32\""} isort = ">=4.2.5,<6" mccabe = ">=0.6,<0.7" @@ -921,7 +929,7 @@ python-versions = ">=3.5" [[package]] name = "pytest" -version = "6.2.2" +version = "6.2.3" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -1112,7 +1120,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.2" +version = "3.5.3" description = "Python documentation generator" category = "dev" optional = false @@ -1271,7 +1279,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tqdm" -version = "4.59.0" +version = "4.60.0" description = "Fast, Extensible Progress Meter" category = "dev" optional = false @@ -1308,16 +1316,16 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "urllib3" -version = "1.26.3" +version = "1.26.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "wcwidth" @@ -1348,7 +1356,7 @@ python-versions = "*" [[package]] name = "wsrpc-aiohttp" -version = "3.1.1" +version = "3.1.2" description = "WSRPC is the RPC over WebSocket for aiohttp" category = "main" optional = false @@ -1391,7 +1399,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "4905515073ad2bf2a8517d513d68e48669b6a829f24e540b2dd60bc70cbea26b" +content-hash = "a8c9915ce3096b74b9328a632911a759780844d368fa1d6d0fbd7c5d7d4536cf" [metadata.files] acre = [] @@ -1455,8 +1463,8 @@ arrow = [ {file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"}, ] astroid = [ - {file = "astroid-2.5.1-py3-none-any.whl", hash = "sha256:21d735aab248253531bb0f1e1e6d068f0ee23533e18ae8a6171ff892b98297cf"}, - {file = "astroid-2.5.1.tar.gz", hash = "sha256:cfc35498ee64017be059ceffab0a25bedf7548ab76f2bea691c5565896e7128d"}, + {file = "astroid-2.5.2-py3-none-any.whl", hash = "sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"}, + {file = "astroid-2.5.2.tar.gz", hash = "sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9"}, ] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, @@ -1471,8 +1479,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] autopep8 = [ - {file = "autopep8-1.5.5-py2.py3-none-any.whl", hash = "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea"}, - {file = "autopep8-1.5.5.tar.gz", hash = "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"}, + {file = "autopep8-1.5.6-py2.py3-none-any.whl", hash = "sha256:f01b06a6808bc31698db907761e5890eb2295e287af53f6693b39ce55454034a"}, + {file = "autopep8-1.5.6.tar.gz", hash = "sha256:5454e6e9a3d02aae38f866eec0d9a7de4ab9f93c10a273fb0340f3d6d09f7514"}, ] babel = [ {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, @@ -1549,6 +1557,10 @@ commonmark = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] +coolname = [ + {file = "coolname-1.1.0-py2.py3-none-any.whl", hash = "sha256:e6a83a0ac88640f4f3d2070438dbe112fe80cfebc119c93bd402976ec84c0978"}, + {file = "coolname-1.1.0.tar.gz", hash = "sha256:410fe6ea9999bf96f2856ef0c726d5f38782bbefb7bb1aca0e91e0dc98ed09e3"}, +] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, @@ -1604,18 +1616,18 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] cryptography = [ - {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, - {file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"}, - {file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"}, - {file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, - {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] cx-freeze = [ {file = "cx_Freeze-6.5.3-cp36-cp36m-win32.whl", hash = "sha256:0a1babae574546b622303da53e1a9829aa3a7e53e62b41eb260250220f83164b"}, @@ -1633,15 +1645,15 @@ dnspython = [ {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, ] docutils = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, + {file = "docutils-0.17-py2.py3-none-any.whl", hash = "sha256:a71042bb7207c03d5647f280427f14bfbd1a65c9eb84f4b341d85fafb6bb4bdf"}, + {file = "docutils-0.17.tar.gz", hash = "sha256:e2ffeea817964356ba4470efba7c2f42b6b0de0b04e66378507e3e2504bbff4c"}, ] evdev = [ {file = "evdev-1.4.0.tar.gz", hash = "sha256:8782740eb1a86b187334c07feb5127d3faa0b236e113206dfe3ae8f77fb1aaf1"}, ] flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, + {file = "flake8-3.9.0-py2.py3-none-any.whl", hash = "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff"}, + {file = "flake8-3.9.0.tar.gz", hash = "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0"}, ] ftrack-python-api = [ {file = "ftrack-python-api-2.0.0.tar.gz", hash = "sha256:dd6f02c31daf5a10078196dc9eac4671e4297c762fbbf4df98de668ac12281d9"}, @@ -1651,16 +1663,16 @@ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] google-api-core = [ - {file = "google-api-core-1.26.1.tar.gz", hash = "sha256:23b0df512c4cc8729793f8992edb350e3211f5fd0ec007afb1599864b421beef"}, - {file = "google_api_core-1.26.1-py2.py3-none-any.whl", hash = "sha256:c383206f0f87545d3e658c4f8dc3b18a8457610fdbd791a15757c5b42d1e0e7f"}, + {file = "google-api-core-1.26.3.tar.gz", hash = "sha256:b914345c7ea23861162693a27703bab804a55504f7e6e9abcaff174d80df32ac"}, + {file = "google_api_core-1.26.3-py2.py3-none-any.whl", hash = "sha256:099762d4b4018cd536bcf85136bf337957da438807572db52f21dc61251be089"}, ] google-api-python-client = [ {file = "google-api-python-client-1.12.8.tar.gz", hash = "sha256:f3b9684442eec2cfe9f9bb48e796ef919456b82142c7528c5fd527e5224f08bb"}, {file = "google_api_python_client-1.12.8-py2.py3-none-any.whl", hash = "sha256:3c4c4ca46b5c21196bec7ee93453443e477d82cbfa79234d1ce0645f81170eaf"}, ] google-auth = [ - {file = "google-auth-1.27.1.tar.gz", hash = "sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"}, - {file = "google_auth-1.27.1-py2.py3-none-any.whl", hash = "sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f"}, + {file = "google-auth-1.28.0.tar.gz", hash = "sha256:9bd436d19ab047001a1340720d2b629eb96dd503258c524921ec2af3ee88a80e"}, + {file = "google_auth-1.28.0-py2.py3-none-any.whl", hash = "sha256:dcaba3aa9d4e0e96fd945bf25a86b6f878fcb05770b67adbeb50a63ca4d28a5e"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, @@ -1671,8 +1683,8 @@ googleapis-common-protos = [ {file = "googleapis_common_protos-1.53.0-py2.py3-none-any.whl", hash = "sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0"}, ] httplib2 = [ - {file = "httplib2-0.19.0-py3-none-any.whl", hash = "sha256:749c32603f9bf16c1277f59531d502e8f1c2ca19901ae653b49c4ed698f0820e"}, - {file = "httplib2-0.19.0.tar.gz", hash = "sha256:e0d428dad43c72dbce7d163b7753ffc7a39c097e6788ef10f4198db69b92f08e"}, + {file = "httplib2-0.19.1-py3-none-any.whl", hash = "sha256:2ad195faf9faf079723f6714926e9a9061f694d07724b846658ce08d40f522b4"}, + {file = "httplib2-0.19.1.tar.gz", hash = "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -1683,16 +1695,16 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.7.2-py3-none-any.whl", hash = "sha256:407d13f55dc6f2a844e62325d18ad7019a436c4bfcaee34cda35f2be6e7c3e34"}, - {file = "importlib_metadata-3.7.2.tar.gz", hash = "sha256:18d5ff601069f98d5d605b6a4b50c18a34811d655c55548adc833e687289acde"}, + {file = "importlib_metadata-3.10.0-py3-none-any.whl", hash = "sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe"}, + {file = "importlib_metadata-3.10.0.tar.gz", hash = "sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] isort = [ - {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, - {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jedi = [ {file = "jedi-0.13.3-py2.py3-none-any.whl", hash = "sha256:2c6bcd9545c7d6440951b12b44d373479bf18123a401a52025cf98563fbd826c"}, @@ -1719,30 +1731,28 @@ keyring = [ {file = "keyring-22.4.0.tar.gz", hash = "sha256:d981e02d134cc3d636a716fbc3ca967bc9609bae5dc21b0063e4409355993ddf"}, ] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.5.2.tar.gz", hash = "sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4"}, - {file = "lazy_object_proxy-1.5.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e960e8be509e8d6d618300a6c189555c24efde63e85acaf0b14b2cd1ac743315"}, - {file = "lazy_object_proxy-1.5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:522b7c94b524389f4a4094c4bf04c2b02228454ddd17c1a9b2801fac1d754871"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3782931963dc89e0e9a0ae4348b44762e868ea280e4f8c233b537852a8996ab9"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:429c4d1862f3fc37cd56304d880f2eae5bd0da83bdef889f3bd66458aac49128"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-win32.whl", hash = "sha256:cd1bdace1a8762534e9a36c073cd54e97d517a17d69a17985961265be6d22847"}, - {file = "lazy_object_proxy-1.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:ddbdcd10eb999d7ab292677f588b658372aadb9a52790f82484a37127a390108"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ecb5dd5990cec6e7f5c9c1124a37cb2c710c6d69b0c1a5c4aa4b35eba0ada068"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b6577f15d5516d7d209c1a8cde23062c0f10625f19e8dc9fb59268859778d7d7"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-win32.whl", hash = "sha256:c8fe2d6ff0ff583784039d0255ea7da076efd08507f2be6f68583b0da32e3afb"}, - {file = "lazy_object_proxy-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:fa5b2dee0e231fa4ad117be114251bdfe6afe39213bd629d43deb117b6a6c40a"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1d33d6f789697f401b75ce08e73b1de567b947740f768376631079290118ad39"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:57fb5c5504ddd45ed420b5b6461a78f58cbb0c1b0cbd9cd5a43ad30a4a3ee4d0"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-win32.whl", hash = "sha256:e7273c64bccfd9310e9601b8f4511d84730239516bada26a0c9846c9697617ef"}, - {file = "lazy_object_proxy-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f4e5e68b7af950ed7fdb594b3f19a0014a3ace0fedb86acb896e140ffb24302"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cadfa2c2cf54d35d13dc8d231253b7985b97d629ab9ca6e7d672c35539d38163"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e7428977763150b4cf83255625a80a23dfdc94d43be7791ce90799d446b4e26f"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-win32.whl", hash = "sha256:2f2de8f8ac0be3e40d17730e0600619d35c78c13a099ea91ef7fb4ad944ce694"}, - {file = "lazy_object_proxy-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:38c3865bd220bd983fcaa9aa11462619e84a71233bafd9c880f7b1cb753ca7fa"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8a44e9901c0555f95ac401377032f6e6af66d8fc1fbfad77a7a8b1a826e0b93c"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fa7fb7973c622b9e725bee1db569d2c2ee64d2f9a089201c5e8185d482c7352d"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:71a1ef23f22fa8437974b2d60fedb947c99a957ad625f83f43fd3de70f77f458"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-win32.whl", hash = "sha256:ef3f5e288aa57b73b034ce9c1f1ac753d968f9069cd0742d1d69c698a0167166"}, - {file = "lazy_object_proxy-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:37d9c34b96cca6787fe014aeb651217944a967a5b165e2cacb6b858d2997ab84"}, + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, @@ -1850,73 +1860,73 @@ packaging = [ {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] parso = [ - {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, - {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, + {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, + {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, ] pathlib2 = [ {file = "pathlib2-2.3.5-py2.py3-none-any.whl", hash = "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db"}, {file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"}, ] pillow = [ - {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"}, - {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"}, - {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8"}, - {file = "Pillow-8.1.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34"}, - {file = "Pillow-8.1.2-cp36-cp36m-win32.whl", hash = "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71"}, - {file = "Pillow-8.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341"}, - {file = "Pillow-8.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6"}, - {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28"}, - {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d"}, - {file = "Pillow-8.1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be"}, - {file = "Pillow-8.1.2-cp37-cp37m-win32.whl", hash = "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55"}, - {file = "Pillow-8.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03"}, - {file = "Pillow-8.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e"}, - {file = "Pillow-8.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce"}, - {file = "Pillow-8.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af"}, - {file = "Pillow-8.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06"}, - {file = "Pillow-8.1.2-cp38-cp38-win32.whl", hash = "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09"}, - {file = "Pillow-8.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060"}, - {file = "Pillow-8.1.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1"}, - {file = "Pillow-8.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6"}, - {file = "Pillow-8.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238"}, - {file = "Pillow-8.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910"}, - {file = "Pillow-8.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1"}, - {file = "Pillow-8.1.2-cp39-cp39-win32.whl", hash = "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e"}, - {file = "Pillow-8.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0"}, - {file = "Pillow-8.1.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b"}, - {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2"}, - {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733"}, - {file = "Pillow-8.1.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a"}, - {file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"}, + {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"}, + {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"}, + {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"}, + {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"}, + {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"}, + {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"}, + {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"}, + {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"}, + {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"}, + {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"}, + {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"}, + {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"}, + {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"}, + {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"}, + {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"}, + {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"}, + {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"}, + {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"}, + {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"}, + {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] protobuf = [ - {file = "protobuf-3.15.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1771ef20e88759c4d81db213e89b7a1fc53937968e12af6603c658ee4bcbfa38"}, - {file = "protobuf-3.15.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1a66261a402d05c8ad8c1fde8631837307bf8d7e7740a4f3941fc3277c2e1528"}, - {file = "protobuf-3.15.6-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:eac23a3e56175b710f3da9a9e8e2aa571891fbec60e0c5a06db1c7b1613b5cfd"}, - {file = "protobuf-3.15.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ec220d90eda8bb7a7a1434a8aed4fe26d7e648c1a051c2885f3f5725b6aa71a"}, - {file = "protobuf-3.15.6-cp35-cp35m-win32.whl", hash = "sha256:88d8f21d1ac205eedb6dea943f8204ed08201b081dba2a966ab5612788b9bb1e"}, - {file = "protobuf-3.15.6-cp35-cp35m-win_amd64.whl", hash = "sha256:eaada29bbf087dea7d8bce4d1d604fc768749e8809e9c295922accd7c8fce4d5"}, - {file = "protobuf-3.15.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:256c0b2e338c1f3228d3280707606fe5531fde85ab9d704cde6fdeb55112531f"}, - {file = "protobuf-3.15.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b9069e45b6e78412fba4a314ea38b4a478686060acf470d2b131b3a2c50484ec"}, - {file = "protobuf-3.15.6-cp36-cp36m-win32.whl", hash = "sha256:24f4697f57b8520c897a401b7f9a5ae45c369e22c572e305dfaf8053ecb49687"}, - {file = "protobuf-3.15.6-cp36-cp36m-win_amd64.whl", hash = "sha256:d9ed0955b794f1e5f367e27f8a8ff25501eabe34573f003f06639c366ca75f73"}, - {file = "protobuf-3.15.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:822ac7f87fc2fb9b24edd2db390538b60ef50256e421ca30d65250fad5a3d477"}, - {file = "protobuf-3.15.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:74ac159989e2b02d761188a2b6f4601ff5e494d9b9d863f5ad6e98e5e0c54328"}, - {file = "protobuf-3.15.6-cp37-cp37m-win32.whl", hash = "sha256:30fe4249a364576f9594180589c3f9c4771952014b5f77f0372923fc7bafbbe2"}, - {file = "protobuf-3.15.6-cp37-cp37m-win_amd64.whl", hash = "sha256:45a91fc6f9aa86d3effdeda6751882b02de628519ba06d7160daffde0c889ff8"}, - {file = "protobuf-3.15.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83c7c7534f050cb25383bb817159416601d1cc46c40bc5e851ec8bbddfc34a2f"}, - {file = "protobuf-3.15.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9ec20a6ded7d0888e767ad029dbb126e604e18db744ac0a428cf746e040ccecd"}, - {file = "protobuf-3.15.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f2da2fcc4102b6c3b57f03c9d8d5e37c63f8bc74deaa6cb54e0cc4524a77247"}, - {file = "protobuf-3.15.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:70054ae1ce5dea7dec7357db931fcf487f40ea45b02cb719ee6af07eb1e906fb"}, - {file = "protobuf-3.15.6-py2.py3-none-any.whl", hash = "sha256:1655fc0ba7402560d749de13edbfca1ac45d1753d8f4e5292989f18f5a00c215"}, - {file = "protobuf-3.15.6.tar.gz", hash = "sha256:2b974519a2ae83aa1e31cff9018c70bbe0e303a46a598f982943c49ae1d4fcd3"}, + {file = "protobuf-3.15.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a14141d5c967362d2eedff8825d2b69cc36a5b3ed6b1f618557a04e58a3cf787"}, + {file = "protobuf-3.15.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d54d78f621852ec4fdd1484d1263ca04d4bf5ffdf7abffdbb939e444b6ff3385"}, + {file = "protobuf-3.15.7-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:462085acdb410b06335315fe7e63cb281a1902856e0f4657f341c283cedc1d56"}, + {file = "protobuf-3.15.7-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:849c92ce112e1ef648705c29ce044248e350f71d9d54a2026830623198f0bd38"}, + {file = "protobuf-3.15.7-cp35-cp35m-win32.whl", hash = "sha256:1f6083382f7714700deadf3014e921711e2f807de7f27e40c32b744701ae5b99"}, + {file = "protobuf-3.15.7-cp35-cp35m-win_amd64.whl", hash = "sha256:e17f60f00081adcb32068ee0bb51e418f6474acf83424244ff3512ffd2166385"}, + {file = "protobuf-3.15.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c75e563c6fb2ca5b8f21dd75c15659aa2c4a0025b9da3a7711ae661cd6a488d"}, + {file = "protobuf-3.15.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d939f41b4108350841c4790ebbadb61729e1363522fdb8434eb4e6f2065d0db1"}, + {file = "protobuf-3.15.7-cp36-cp36m-win32.whl", hash = "sha256:24f14c09d4c0a3641f1b0e9b552d026361de65b01686fdd3e5fdf8f9512cd79b"}, + {file = "protobuf-3.15.7-cp36-cp36m-win_amd64.whl", hash = "sha256:1247170191bcb2a8d978d11a58afe391004ec6c2184e4d961baf8102d43ff500"}, + {file = "protobuf-3.15.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:364cadaeec0756afdc099cbd88cb5659bd1bb7d547168d063abcb0272ccbb2f6"}, + {file = "protobuf-3.15.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c3a6941b1e6e6e22d812a8e5c46bfe83082ea60d262a46f2cfb22d9b9fb17db"}, + {file = "protobuf-3.15.7-cp37-cp37m-win32.whl", hash = "sha256:eb5668f3f6a83b6603ca2e09be5b20de89521ea5914aabe032cce981e4129cc8"}, + {file = "protobuf-3.15.7-cp37-cp37m-win_amd64.whl", hash = "sha256:1001e671cf8476edce7fb72778358d026390649cc35a79d47b2a291684ccfbb2"}, + {file = "protobuf-3.15.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a5ba7dd6f97964655aa7b234c95d80886425a31b7010764f042cdeb985314d18"}, + {file = "protobuf-3.15.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:46674bd6fcf8c63b4b9869ba579685db67cf51ae966443dd6bd9a8fa00fcef62"}, + {file = "protobuf-3.15.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4c4399156fb27e3768313b7a59352c861a893252bda6fb9f3643beb3ebb7047e"}, + {file = "protobuf-3.15.7-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:85cd29faf056036167d87445d5a5059034c298881c044e71a73d3b61a4be1c23"}, + {file = "protobuf-3.15.7-py2.py3-none-any.whl", hash = "sha256:22054432b923c0086f9cf1e1c0c52d39bf3c6e31014ea42eec2dabc22ee26d78"}, + {file = "protobuf-3.15.7.tar.gz", hash = "sha256:2d03fc2591543cd2456d0b72230b50c4519546a8d379ac6fd3ecd84c6df61e5d"}, ] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, @@ -1960,8 +1970,8 @@ pyblish-base = [ {file = "pyblish_base-1.8.8-py2.py3-none-any.whl", hash = "sha256:67ea253a05d007ab4a175e44e778928ea7bdb0e9707573e1100417bbf0451a53"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, @@ -1973,16 +1983,16 @@ pydocstyle = [ {file = "pydocstyle-3.0.0.tar.gz", hash = "sha256:5741c85e408f9e0ddf873611085e819b809fca90b619f5fd7f34bd4959da3dd4"}, ] pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, ] pylint = [ - {file = "pylint-2.7.2-py3-none-any.whl", hash = "sha256:d09b0b07ba06bcdff463958f53f23df25e740ecd81895f7d2699ec04bbd8dc3b"}, - {file = "pylint-2.7.2.tar.gz", hash = "sha256:0e21d3b80b96740909d77206d741aa3ce0b06b41be375d92e1f3244a274c1f8a"}, + {file = "pylint-2.7.4-py3-none-any.whl", hash = "sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a"}, + {file = "pylint-2.7.4.tar.gz", hash = "sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"}, ] pymongo = [ {file = "pymongo-3.11.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:4d959e929cec805c2bf391418b1121590b4e7d5cb00af7b1ba521443d45a0918"}, @@ -2123,8 +2133,8 @@ pyrsistent = [ {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pytest = [ - {file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"}, - {file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"}, + {file = "pytest-6.2.3-py3-none-any.whl", hash = "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"}, + {file = "pytest-6.2.3.tar.gz", hash = "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634"}, ] pytest-cov = [ {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, @@ -2198,8 +2208,8 @@ speedcopy = [ {file = "speedcopy-2.1.0.tar.gz", hash = "sha256:8bb1a6c735900b83901a7be84ba2175ed3887c13c6786f97dea48f2ea7d504c2"}, ] sphinx = [ - {file = "Sphinx-3.5.2-py3-none-any.whl", hash = "sha256:ef64a814576f46ec7de06adf11b433a0d6049be007fefe7fd0d183d28b581fac"}, - {file = "Sphinx-3.5.2.tar.gz", hash = "sha256:672cfcc24b6b69235c97c750cb190a44ecd72696b4452acaf75c2d9cc78ca5ff"}, + {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, + {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, ] sphinx-qt-documentation = [ {file = "sphinx_qt_documentation-0.3-py3-none-any.whl", hash = "sha256:bee247cb9e4fc03fc496d07adfdb943100e1103320c3e5e820e0cfa7c790d9b6"}, @@ -2245,8 +2255,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tqdm = [ - {file = "tqdm-4.59.0-py2.py3-none-any.whl", hash = "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7"}, - {file = "tqdm-4.59.0.tar.gz", hash = "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33"}, + {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, + {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, ] typed-ast = [ {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, @@ -2290,8 +2300,8 @@ uritemplate = [ {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, ] urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2305,8 +2315,8 @@ wrapt = [ {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, ] wsrpc-aiohttp = [ - {file = "wsrpc-aiohttp-3.1.1.tar.gz", hash = "sha256:a17e1d91624a437e759d4f276b73de1db2071b1681e992cade025e91d31b2a9f"}, - {file = "wsrpc_aiohttp-3.1.1-py3-none-any.whl", hash = "sha256:f3f1ee31aed5145a7fafe8d6c778b914b7e6ec131500395c9c85b0d8676f7302"}, + {file = "wsrpc-aiohttp-3.1.2.tar.gz", hash = "sha256:891164dfe06a8d8d846b485d04b1e56b2c397ff1b46ef0348e6f62bd8efb1693"}, + {file = "wsrpc_aiohttp-3.1.2-py3-none-any.whl", hash = "sha256:4ba64e02b12dcbc09d02544f35bceba49bd04cbc496db47aa8559ae4609ada8e"}, ] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, diff --git a/pyproject.toml b/pyproject.toml index ec2d9c7e3b..6df6db5a18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ acre = { git = "https://github.com/pypeclub/acre.git" } opentimelineio = { version = "0.14.0.dev1", source = "openpype" } appdirs = "^1.4.3" blessed = "^1.17" # openpype terminal formatting +coolname = "*" clique = "1.5.*" Click = "^7" dnspython = "^2.1.0" diff --git a/start.py b/start.py index 1f946a705c..a892d3de8e 100644 --- a/start.py +++ b/start.py @@ -296,7 +296,7 @@ def _determine_mongodb() -> str: if not openpype_mongo: # try system keyring try: - openpype_mongo = bootstrap.registry.get_secure_item( + openpype_mongo = bootstrap.secure_registry.get_item( "openPypeMongo") except ValueError: print("*** No DB connection string specified.") @@ -305,7 +305,7 @@ def _determine_mongodb() -> str: igniter.open_dialog() try: - openpype_mongo = bootstrap.registry.get_secure_item( + openpype_mongo = bootstrap.secure_registry.get_item( "openPypeMongo") except ValueError: raise RuntimeError("missing mongodb url") @@ -326,6 +326,7 @@ def _initialize_environment(openpype_version: OpenPypeVersion) -> None: # TODO move additional paths to `boot` part when OPENPYPE_ROOT will point # to same hierarchy from code and from frozen OpenPype additional_paths = [ + os.environ["OPENPYPE_ROOT"], # add OpenPype tools os.path.join(os.environ["OPENPYPE_ROOT"], "openpype", "tools"), # add common OpenPype vendor diff --git a/tests/igniter/test_bootstrap_repos.py b/tests/igniter/test_bootstrap_repos.py index 75996b4026..6c70380ab6 100644 --- a/tests/igniter/test_bootstrap_repos.py +++ b/tests/igniter/test_bootstrap_repos.py @@ -11,7 +11,7 @@ import pytest from igniter.bootstrap_repos import BootstrapRepos from igniter.bootstrap_repos import PypeVersion -from pype.lib import PypeSettingsRegistry +from pype.lib import OpenPypeSettingsRegistry @pytest.fixture @@ -348,7 +348,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): return d_path.as_posix() monkeypatch.setattr(appdirs, "user_data_dir", mock_user_data_dir) - fix_bootstrap.registry = PypeSettingsRegistry() + fix_bootstrap.registry = OpenPypeSettingsRegistry() fix_bootstrap.registry.set_item("pypePath", d_path.as_posix()) result = fix_bootstrap.find_pype(include_zips=True) diff --git a/tests/pype/lib/test_user_settings.py b/tests/pype/lib/test_user_settings.py index 7f0f400f59..02342abbc9 100644 --- a/tests/pype/lib/test_user_settings.py +++ b/tests/pype/lib/test_user_settings.py @@ -1,10 +1,20 @@ import pytest -from pype.lib import IniSettingRegistry -from pype.lib import JSONSettingRegistry +from pype.lib import ( + IniSettingRegistry, + JSONSettingRegistry, + OpenPypeSecureRegistry +) from uuid import uuid4 import configparser +@pytest.fixture +def secure_registry(tmpdir): + name = "pypetest_{}".format(str(uuid4())) + r = OpenPypeSecureRegistry(name, tmpdir) + yield r + + @pytest.fixture def json_registry(tmpdir): name = "pypetest_{}".format(str(uuid4())) @@ -19,21 +29,21 @@ def ini_registry(tmpdir): yield r -def test_keyring(json_registry): - json_registry.set_secure_item("item1", "foo") - json_registry.set_secure_item("item2", "bar") - result1 = json_registry.get_secure_item("item1") - result2 = json_registry.get_secure_item("item2") +def test_keyring(secure_registry): + secure_registry.set_item("item1", "foo") + secure_registry.set_item("item2", "bar") + result1 = secure_registry.get_item("item1") + result2 = secure_registry.get_item("item2") assert result1 == "foo" assert result2 == "bar" - json_registry.delete_secure_item("item1") - json_registry.delete_secure_item("item2") + secure_registry.delete_item("item1") + secure_registry.delete_item("item2") with pytest.raises(ValueError): - json_registry.get_secure_item("item1") - json_registry.get_secure_item("item2") + secure_registry.get_item("item1") + secure_registry.get_item("item2") def test_ini_registry(ini_registry): diff --git a/website/docs/artist_ftrack.md b/website/docs/artist_ftrack.md index e42136fa89..2210615160 100644 --- a/website/docs/artist_ftrack.md +++ b/website/docs/artist_ftrack.md @@ -6,55 +6,106 @@ sidebar_label: Artist # How to use Ftrack in OpenPype -## Login to Ftrack module in OpenPype tray (best case scenario) -1. Launch OpenPype tray if not launched yet -2. *Ftrack login* window pop up on start - - or press **login** in **Ftrack menu** to pop up *Ftrack login* window +## Login to Ftrack module in OpenPype (best case scenario) +1. Launch OpenPype and go to systray OpenPype icon. +2. *Ftrack login* window pop up on start or press **login** in **Ftrack menu** to pop up *Ftrack login* window  -3. Press `Ftrack` button - -4. Web browser opens -5. Sign in Ftrack if you're requested + - Press `Ftrack` button + +  + - Web browser opens + + - Sign into Ftrack if requested. If you are already signed in to Ftrack via web browser, you can jump to [Application launch](#application-launch-best-case-scenario) - -6. Message is shown +  + +3. Message is shown  -7. Close message and you're ready to use actions - continue with [Application launch](#application-launch-best-case-scenario) + +4. Close message and you're ready to use actions - continue with [Application launch](#application-launch-best-case-scenario) + --- + ## Application launch (best case scenario) -1. Make sure OpenPype is running and you passed [Login to Ftrack](#login-to-ftrack-module-in-pype-tray-best-case-scenario) guide -2. Open web browser and go to your studio Ftrack web page *(e.g. https://mystudio.ftrackapp.com/)* -3. Locate the task on which you want to run the application +1. Make sure OpenPype is running and you passed [Login to Ftrack](#login-to-ftrack-module-in-openpype-best-case-scenario) guide + +2. Open Web browser and go to your studio Ftrack web page *(e.g. https://mystudio.ftrackapp.com/)* + +3. Locate the task to run the application on. + 4. Display actions for the task  + 5. Select application you want to launch - - application versions may be grouped to one action in that case press the action to reveal versions to choose *(like Maya in the picture)* + - application versions may be grouped to one action. In that case, press the action to reveal versions to choose from *(like Maya in the picture)*, only applications permitted on the particular project will appear.  -6. Work + +6. Start working ;) --- + ## Change Ftrack user 1. Log out the previous user from Ftrack Web app *(skip if new is already logged)* - -2. Log out the previous user from Ftrack module in tray +  + +2. Log out the previous user from Ftrack module in OpenPype tray + +