Merge branch 'develop' into feature/settings_entities

This commit is contained in:
iLLiCiTiT 2021-01-26 11:45:57 +01:00
commit d04007f962
321 changed files with 8019 additions and 1634 deletions

9
.gitmodules vendored
View file

@ -2,22 +2,15 @@
path = repos/avalon-core
url = git@github.com:pypeclub/avalon-core.git
branch = develop
[submodule "repos/pyblish-base"]
path = repos/pyblish-base
url = git@github.com:pyblish/pyblish-base.git
[submodule "repos/avalon-unreal-integration"]
path = repos/avalon-unreal-integration
url = git@github.com:pypeclub/avalon-unreal-integration.git
[submodule "repos/maya-look-assigner"]
path = repos/maya-look-assigner
url = git@github.com:pypeclub/maya-look-assigner.git
[submodule "repos/acre"]
path = repos/acre
url = git@github.com:antirotor/acre.git
branch = fix/unformatted-tokens
[submodule "pype/modules/ftrack/python2_vendor/ftrack-python-api"]
path = pype/modules/ftrack/python2_vendor/ftrack-python-api
url = https://bitbucket.org/ftrack/ftrack-python-api.git
[submodule "pype/modules/ftrack/python2_vendor/arrow"]
path = pype/modules/ftrack/python2_vendor/arrow
url = git@github.com:arrow-py/arrow.git
url = git@github.com:arrow-py/arrow.git

View file

@ -1,33 +1,106 @@
# Pype
## Introduction
Pype
====
Multi-platform open-source pipeline built around the [Avalon](https://getavalon.github.io/) platform, expanding it with extra features and integrations. Pype connects asset database, project management and time tracking into a single modular system. It has tight integration with [ftrack](https://www.ftrack.com/en/), but it can also run independently.
Introduction
------------
Multi-platform open-source pipeline built around the [Avalon](https://getavalon.github.io/) platform,
expanding it with extra features and integrations. Pype connects asset database, project management
and time tracking into a single modular system. It has tight integration
with [ftrack](https://www.ftrack.com/en/), but it can also run independently.
To get all the key information about the project, go to [PYPE.club](http://pype.club)
## Hardware requirements
Requirements
------------
Pype will run on most typical hardware configurations commonly found in studios around the world.
It is installed on artist computer and can take up 3Gb of space depending on number of versions
and other dependencies.
Pype should be installed centrally on a fast network storage with at least read access right for all workstations and users in the Studio. Full Deplyoyment with all dependencies and both Development and Production branches installed takes about 1GB of data, however to ensure smooth updates and general working comfort, we recommend allocating at least at least 4GB of storage dedicated to PYPE deployment.
For well functioning [ftrack](https://www.ftrack.com/en/) event server, we recommend a
linux virtual server with [Ubuntu](https://ubuntu.com/) or [CentosOS](https://www.centos.org/).
CPU and RAM allocation need differ based on the studio size, but a 2GB of RAM, with a
dual core CPU and around 4GB of storage should suffice.
For well functioning [ftrack](https://www.ftrack.com/en/) event server, we recommend a linux virtual server with [Ubuntu](https://ubuntu.com/) or [CentosOS](https://www.centos.org/). CPU and RAM allocation need differ based on the studio size, but a 2GB of RAM, with a dual core CPU and around 4GB of storage should suffice.
Pype needs running [mongodb](https://www.mongodb.com/) server with good connectivity as it is
heavily used by Pype. Depending on project size and number of artists working connection speed and
latency influence performance experienced by artists. If remote working is required, this mongodb
server must be accessible from Internet or cloud solution can be used. Reasonable backup plan
or high availability options are recommended.
## Building Pype
Building Pype
-------------
### Windows
You will need [Python 3.7 and newer](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads).
More tools might be needed for installing dependencies (for example for **OpenTimelineIO**) - mostly
development tools like [CMake](https://cmake.org/) and [Visual Studio](https://visualstudio.microsoft.com/cs/downloads/)
Clone repository:
```sh
git clone --recurse-submodules git@github.com:pypeclub/pype.git
```
Run PowerShell script `build.ps1`. It will create *venv*, install all
required dependencies and build Pype. After it is finished, you will find
Pype in `build` folder.
#### To build Pype:
You might need more tools for installing dependencies (for example for **OpenTimelineIO**) - mostly
development tools like [CMake](https://cmake.org/) and [Visual Studio](https://visualstudio.microsoft.com/cs/downloads/)
1) Run `.\tools\create_env.ps1` to create virtual environment in `.\venv`
2) Run `.\tools\build.ps1` to build pype executables in `.\build\`
To create distributable Pype versions, run `./tools/create_zip.ps1` - that will
create zip file with name `pype-vx.x.x.zip` parsed from current pype repository and
copy it to user data dir, or you can specify `--path /path/to/zip` to force it there.
You can then point **Igniter** - Pype setup tool - to directory containing this zip and
it will install it on current computer.
Pype is build using [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest) to freeze itself and all dependencies.
Running Pype
------------
Pype can by executed either from live sources (this repository) or from
*"frozen code"* - executables that can be build using steps described above.
If Pype is executed from live sources, it will use Pype version included in them. If
it is executed from frozen code it will try to find latest Pype version installed locally
on current computer and if it is not found, it will ask for its location. On that location
pype can be either in directories or zip files. Pype will try to find latest version and
install it to user data directory (on Windows to `%LOCALAPPDATA%\pypeclub\pype`).
### From sources
Pype can be run directly from sources by activating virtual environment:
```powershell
.\venv\Scripts\Activate.ps1
```
and running:
```powershell
python start.py tray
```
This will use current Pype version with sources. You can override this with `--use-version=x.x.x` and
then Pype will try to find locally installed specified version (present in user data directory).
### From frozen code
You need to build Pype first. This will produce two executables - `pype.exe` and `pype_console.exe`.
First one will act as GUI application and will not create console (useful in production environments).
The second one will create console and will write output there - useful for headless application and
debugging purposes. If you need pype version installed, just run `./tools/create_zip.ps1` without
arguments and it will create zip file that pype can use.
Building documentation
----------------------
Top build API documentation, run `.\tools\make_docs.ps1`. It will create html documentation
from current sources in `.\docs\build`.
**Note that it needs existing virtual environment.**
Running tests
-------------
To run tests, execute `.\tools\run_tests.ps1`.
**Note that it needs existing virtual environment.**

View file

@ -1,2 +0,0 @@
.\venv\Scripts\Activate.ps1
python pype.py mongodb

View file

@ -1,2 +0,0 @@
.\venv\Scripts\Activate.ps1
python pype.py settings --dev

View file

@ -1,2 +0,0 @@
.\venv\Scripts\Activate.ps1
python pype.py tray --debug

View file

@ -0,0 +1,7 @@
pype.hosts.aftereffects package
===============================
.. automodule:: pype.hosts.aftereffects
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.hosts.resolve.otio.davinci\_export module
==============================================
.. automodule:: pype.hosts.resolve.otio.davinci_export
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.hosts.resolve.otio.davinci\_import module
==============================================
.. automodule:: pype.hosts.resolve.otio.davinci_import
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,17 @@
pype.hosts.resolve.otio package
===============================
.. automodule:: pype.hosts.resolve.otio
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
:maxdepth: 10
pype.hosts.resolve.otio.davinci_export
pype.hosts.resolve.otio.davinci_import
pype.hosts.resolve.otio.utils

View file

@ -0,0 +1,7 @@
pype.hosts.resolve.otio.utils module
====================================
.. automodule:: pype.hosts.resolve.otio.utils
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.hosts.resolve.todo\-rendering module
=========================================
.. automodule:: pype.hosts.resolve.todo-rendering
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.hosts.tvpaint.api package
==============================
.. automodule:: pype.hosts.tvpaint.api
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,15 @@
pype.hosts.tvpaint package
==========================
.. automodule:: pype.hosts.tvpaint
:members:
:undoc-members:
:show-inheritance:
Subpackages
-----------
.. toctree::
:maxdepth: 10
pype.hosts.tvpaint.api

View file

@ -0,0 +1,7 @@
pype.lib.abstract\_collect\_render module
=========================================
.. automodule:: pype.lib.abstract_collect_render
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.abstract\_expected\_files module
=========================================
.. automodule:: pype.lib.abstract_expected_files
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.abstract\_metaplugins module
=====================================
.. automodule:: pype.lib.abstract_metaplugins
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.abstract\_submit\_deadline module
==========================================
.. automodule:: pype.lib.abstract_submit_deadline
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.applications module
============================
.. automodule:: pype.lib.applications
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.avalon\_context module
===============================
.. automodule:: pype.lib.avalon_context
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.deprecated module
==========================
.. automodule:: pype.lib.deprecated
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.editorial module
=========================
.. automodule:: pype.lib.editorial
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.env\_tools module
==========================
.. automodule:: pype.lib.env_tools
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.ffmpeg\_utils module
=============================
.. automodule:: pype.lib.ffmpeg_utils
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.path\_tools module
===========================
.. automodule:: pype.lib.path_tools
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.plugin\_tools module
=============================
.. automodule:: pype.lib.plugin_tools
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.python\_module\_tools module
=====================================
.. automodule:: pype.lib.python_module_tools
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.lib.terminal\_splash module
================================
.. automodule:: pype.lib.terminal_splash
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.clockify.clockify\_module module
=============================================
.. automodule:: pype.modules.clockify.clockify_module
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.deadline.deadline\_module module
=============================================
.. automodule:: pype.modules.deadline.deadline_module
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,15 @@
pype.modules.deadline package
=============================
.. automodule:: pype.modules.deadline
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
:maxdepth: 10
pype.modules.deadline.deadline_module

View file

@ -0,0 +1,7 @@
pype.modules.ftrack.ftrack\_module module
=========================================
.. automodule:: pype.modules.ftrack.ftrack_module
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.ftrack.lib.settings module
=======================================
.. automodule:: pype.modules.ftrack.lib.settings
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.ftrack.tray.ftrack\_tray module
============================================
.. automodule:: pype.modules.ftrack.tray.ftrack_tray
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.launcher\_action module
====================================
.. automodule:: pype.modules.launcher_action
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.log\_viewer.log\_view\_module module
=================================================
.. automodule:: pype.modules.log_viewer.log_view_module
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,23 @@
pype.modules.log\_viewer package
================================
.. automodule:: pype.modules.log_viewer
:members:
:undoc-members:
:show-inheritance:
Subpackages
-----------
.. toctree::
:maxdepth: 10
pype.modules.log_viewer.tray
Submodules
----------
.. toctree::
:maxdepth: 10
pype.modules.log_viewer.log_view_module

View file

@ -0,0 +1,7 @@
pype.modules.log\_viewer.tray.app module
========================================
.. automodule:: pype.modules.log_viewer.tray.app
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.log\_viewer.tray.models module
===========================================
.. automodule:: pype.modules.log_viewer.tray.models
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,17 @@
pype.modules.log\_viewer.tray package
=====================================
.. automodule:: pype.modules.log_viewer.tray
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
:maxdepth: 10
pype.modules.log_viewer.tray.app
pype.modules.log_viewer.tray.models
pype.modules.log_viewer.tray.widgets

View file

@ -0,0 +1,7 @@
pype.modules.log\_viewer.tray.widgets module
============================================
.. automodule:: pype.modules.log_viewer.tray.widgets
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.settings\_action module
====================================
.. automodule:: pype.modules.settings_action
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.standalonepublish\_action module
=============================================
.. automodule:: pype.modules.standalonepublish_action
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,16 @@
pype.modules.sync\_server package
=================================
.. automodule:: pype.modules.sync_server
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
:maxdepth: 10
pype.modules.sync_server.sync_server
pype.modules.sync_server.utils

View file

@ -0,0 +1,7 @@
pype.modules.sync\_server.sync\_server module
=============================================
.. automodule:: pype.modules.sync_server.sync_server
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.sync\_server.utils module
======================================
.. automodule:: pype.modules.sync_server.utils
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.modules.websocket\_server.hosts.aftereffects module
========================================================
.. automodule:: pype.modules.websocket_server.hosts.aftereffects
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.plugins.maya.publish.validate\_vray\_referenced\_aovs module
=================================================================
.. automodule:: pype.plugins.maya.publish.validate_vray_referenced_aovs
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.settings.constants module
==============================
.. automodule:: pype.settings.constants
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.settings.handlers module
=============================
.. automodule:: pype.settings.handlers
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.tests.test\_lib\_restructuralization module
================================================
.. automodule:: pype.tests.test_lib_restructuralization
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.tools.tray.pype\_tray module
=================================
.. automodule:: pype.tools.tray.pype_tray
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,15 @@
pype.tools.tray package
=======================
.. automodule:: pype.tools.tray
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
:maxdepth: 10
pype.tools.tray.pype_tray

View file

@ -0,0 +1,7 @@
pype.tools.workfiles.app module
===============================
.. automodule:: pype.tools.workfiles.app
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,7 @@
pype.tools.workfiles.model module
=================================
.. automodule:: pype.tools.workfiles.model
:members:
:undoc-members:
:show-inheritance:

View file

@ -0,0 +1,17 @@
pype.tools.workfiles package
============================
.. automodule:: pype.tools.workfiles
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
:maxdepth: 10
pype.tools.workfiles.app
pype.tools.workfiles.model
pype.tools.workfiles.view

View file

@ -0,0 +1,7 @@
pype.tools.workfiles.view module
================================
.. automodule:: pype.tools.workfiles.view
:members:
:undoc-members:
:show-inheritance:

View file

@ -9,8 +9,10 @@ from .bootstrap_repos import BootstrapRepos
def run():
"""Show Igniter dialog."""
app = QtWidgets.QApplication(sys.argv)
d = InstallDialog()
d.exec_()
d.show()
sys.exit(app.exec_())

View file

@ -1,21 +1,20 @@
# -*- coding: utf-8 -*-
"""Bootstrap Pype repositories."""
import sys
import functools
import logging as log
import os
import re
import logging as log
import shutil
import sys
import tempfile
from typing import Union, Callable, List
from zipfile import ZipFile
from pathlib import Path
import functools
from speedcopy import copyfile
from typing import Union, Callable, List, Tuple
from zipfile import ZipFile, BadZipFile
from appdirs import user_data_dir
from pype.version import __version__
from pype.lib import PypeSettingsRegistry
from speedcopy import copyfile
from .user_settings import PypeSettingsRegistry
from .tools import load_environments
@ -24,21 +23,24 @@ class PypeVersion:
"""Class for storing information about Pype version.
Attributes:
major (int): [1].2.3-variant-client
minor (int): 1.[2].3-variant-client
subversion (int): 1.2.[3]-variant-client
variant (str): 1.2.3-[variant]-client
client (str): 1.2.3-variant-[client]
major (int): [1].2.3-client-variant
minor (int): 1.[2].3-client-variant
subversion (int): 1.2.[3]-client-variant
client (str): 1.2.3-[client]-variant
variant (str): 1.2.3-client-[variant]
path (str): path to Pype
"""
major = 0
minor = 0
subversion = 0
variant = "production"
variant = ""
client = None
path = None
_version_regex = re.compile(
r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<sub>\d+)(-(?P<var1>staging)|-(?P<client>.+)(-(?P<var2>staging)))?") # noqa: E501
@property
def version(self):
"""return formatted version string."""
@ -55,15 +57,14 @@ class PypeVersion:
def __init__(self, major: int = None, minor: int = None,
subversion: int = None, version: str = None,
variant: str = "production", client: str = None,
variant: str = "", client: str = None,
path: Path = None):
self.path = path
self._version_regex = re.compile(
r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<sub>\d+)(-?((?P<variant>staging)|(?P<client>.+))(-(?P<cli>.+))?)?") # noqa: E501
if major is None or minor is None or subversion is None:
if version is None:
raise ValueError("Need version specified in some way.")
if (
major is None or minor is None or subversion is None
) and version is None:
raise ValueError("Need version specified in some way.")
if version:
values = self._decompose_version(version)
self.major = values[0]
@ -83,25 +84,27 @@ class PypeVersion:
def _compose_version(self):
version = "{}.{}.{}".format(self.major, self.minor, self.subversion)
if self.variant == "staging":
version = "{}-{}".format(version, self.variant)
if self.client:
version = "{}-{}".format(version, self.client)
if self.variant == "staging":
version = "{}-{}".format(version, self.variant)
return version
def _decompose_version(self, version_string: str) -> tuple:
m = re.match(self._version_regex, version_string)
@classmethod
def _decompose_version(cls, version_string: str) -> tuple:
m = re.search(cls._version_regex, version_string)
if not m:
raise ValueError(
"Cannot parse version string: {}".format(version_string))
variant = None
if m.group("variant") == "staging":
if m.group("var1") == "staging" or m.group("var2") == "staging":
variant = "staging"
client = m.group("client") or m.group("cli")
client = m.group("client")
return (int(m.group("major")), int(m.group("minor")),
int(m.group("sub")), variant, client)
@ -122,21 +125,85 @@ class PypeVersion:
return hash(self.version)
def __lt__(self, other):
if self.major < other.major:
if (self.major, self.minor, self.subversion) < \
(other.major, other.minor, other.subversion):
return True
if self.major <= other.major and self.minor < other.minor:
return True
if self.major <= other.major and self.minor <= other.minor and \
self.subversion < other.subversion:
# 1.2.3-staging < 1.2.3-client-staging
if self.get_main_version() == other.get_main_version() and \
not self.client and self.variant and \
other.client and other.variant:
return True
if self.major == other.major and self.minor == other.minor and \
self.subversion == other.subversion and \
self.variant == "staging":
# 1.2.3 < 1.2.3-staging
if self.get_main_version() == other.get_main_version() and \
not self.client and self.variant and \
not other.client and not other.variant:
return True
return False
# 1.2.3 < 1.2.3-client
if self.get_main_version() == other.get_main_version() and \
not self.client and not self.variant and \
other.client and not other.variant:
return True
# 1.2.3 < 1.2.3-client-staging
if self.get_main_version() == other.get_main_version() and \
not self.client and not self.variant and other.client:
return True
# 1.2.3-client-staging < 1.2.3-client
if self.get_main_version() == other.get_main_version() and \
self.client and self.variant and \
other.client and not other.variant:
return True
# prefer path over no path
if self.version == other.version and \
not self.path and other.path:
return True
# prefer path with dir over path with file
return self.version == other.version and self.path and \
other.path and self.path.is_file() and \
other.path.is_dir()
def is_staging(self) -> bool:
"""Test if current version is staging one."""
return self.variant == "staging"
def get_main_version(self) -> str:
"""Return main version component.
This returns x.x.x part of version from possibly more complex one
like x.x.x-foo-bar.
Returns:
str: main version component
"""
return "{}.{}.{}".format(self.major, self.minor, self.subversion)
@staticmethod
def version_in_str(string: str) -> Tuple:
"""Find Pype version in given string.
Args:
string (str): string to search.
Returns:
tuple: True/False and PypeVersion if found.
"""
try:
result = PypeVersion._decompose_version(string)
except ValueError:
return False, None
return True, PypeVersion(major=result[0],
minor=result[1],
subversion=result[2],
variant=result[3],
client=result[4])
class BootstrapRepos:
@ -146,15 +213,20 @@ class BootstrapRepos:
data_dir (Path): local Pype installation directory.
live_repo_dir (Path): path to repos directory if running live,
otherwise `None`.
registry (PypeSettingsRegistry): Pype registry object.
zip_filter (list): List of files to exclude from zip
pype_filter (list): list of top level directories not to include in
zip in Pype repository.
"""
def __init__(self, progress_callback: Callable = None):
def __init__(self, progress_callback: Callable = None, message=None):
"""Constructor.
Args:
progress_callback (callable): Optional callback method to report
progress.
message (QtCore.Signal, optional): Signal to report messages back.
"""
# vendor and app used to construct user data dir
@ -163,9 +235,15 @@ class BootstrapRepos:
self._log = log.getLogger(str(__class__))
self.data_dir = Path(user_data_dir(self._app, self._vendor))
self.registry = PypeSettingsRegistry()
self.zip_filter = [".pyc", "__pycache__"]
self.pype_filter = [
"build", "docs", "tests", "repos", "tools", "venv"
]
self._message = message
# dummy progress reporter
def empty_progress(x: int):
"""Progress callback dummy."""
return x
if not progress_callback:
@ -194,9 +272,14 @@ class BootstrapRepos:
return v.path
@staticmethod
def get_local_version() -> str:
def get_local_live_version() -> str:
"""Get version of local Pype."""
return __version__
version = {}
path = Path(os.path.dirname(__file__)).parent / "pype" / "version.py"
with open(path, "r") as fp:
exec(fp.read(), version)
return version["__version__"]
@staticmethod
def get_version(repo_dir: Path) -> Union[str, None]:
@ -225,7 +308,7 @@ class BootstrapRepos:
"""Copy zip created from Pype repositories to user data dir.
This detect Pype version either in local "live" Pype repository
or in user provided path. Then it will zip in in temporary directory
or in user provided path. Then it will zip it in temporary directory
and finally it will move it to destination which is user data
directory. Existing files will be replaced.
@ -240,7 +323,7 @@ class BootstrapRepos:
# version and use it as a source. Otherwise repo_dir is user
# entered location.
if not repo_dir:
version = self.get_local_version()
version = self.get_local_live_version()
repo_dir = self.live_repo_dir
else:
version = self.get_version(repo_dir)
@ -252,7 +335,7 @@ class BootstrapRepos:
# create zip inside temporary directory.
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-repositories-v{version}.zip"
Path(temp_dir) / f"pype-v{version}.zip"
self._log.info(f"creating zip: {temp_zip}")
self._create_pype_zip(temp_zip, repo_dir)
@ -275,7 +358,7 @@ class BootstrapRepos:
except shutil.Error as e:
self._log.error(e)
return None
return self.data_dir / temp_zip.name
return destination
def _create_pype_zip(
self,
@ -284,11 +367,9 @@ class BootstrapRepos:
"""Pack repositories and Pype into zip.
We are using :mod:`zipfile` instead :meth:`shutil.make_archive`
to later implement file filter to skip git related stuff to make
it into archive.
Todo:
Implement file filter
because we need to decide what file and directories to include in zip
and what not. They are determined by :attr:`zip_filter` on file level
and :attr:`pype_filter` on top level directory in Pype repository.
Args:
zip_path (str): path to zip file.
@ -296,42 +377,82 @@ class BootstrapRepos:
include_pype (bool): add Pype module itself.
"""
repo_files = sum(len(files) for _, _, files in os.walk(include_dir))
include_dir = include_dir.resolve()
def _filter_dir(path: Path, path_filter: List) -> List[Path]:
"""Recursively crawl over path and filter."""
result = []
for item in path.iterdir():
if item.name in path_filter:
continue
if item.name.startswith('.'):
continue
if item.is_dir():
result.extend(_filter_dir(item, path_filter))
else:
result.append(item)
return result
pype_list = []
# get filtered list of files in repositories (repos directory)
repo_list = _filter_dir(include_dir, self.zip_filter)
# count them
repo_files = len(repo_list)
# there must be some files, otherwise `include_dir` path is wrong
assert repo_files != 0, f"No repositories to include in {include_dir}"
pype_inc = 0
if include_pype:
pype_files = sum(len(files) for _, _, files in os.walk(
include_dir.parent))
# get filtered list of file in Pype repository
pype_list = _filter_dir(include_dir.parent, self.zip_filter)
pype_files = len(pype_list)
repo_inc = 48.0 / float(repo_files)
pype_inc = 48.0 / float(pype_files)
else:
repo_inc = 98.0 / float(repo_files)
progress = 0
with ZipFile(zip_path, "w") as zip_file:
for root, _, files in os.walk(include_dir.as_posix()):
for file in files:
zip_file.write(
os.path.relpath(os.path.join(root, file),
os.path.join(include_dir, '..')),
os.path.relpath(os.path.join(root, file),
os.path.join(include_dir))
)
progress += repo_inc
self._progress_callback(int(progress))
file: Path
for file in repo_list:
progress += repo_inc
self._progress_callback(int(progress))
# archive name is relative to repos dir
arc_name = file.relative_to(include_dir)
zip_file.write(file, arc_name)
# add pype itself
if include_pype:
for root, _, files in os.walk("pype"):
for file in files:
zip_file.write(
os.path.relpath(os.path.join(root, file),
os.path.join('pype', '..')),
os.path.join(
'pype',
os.path.relpath(os.path.join(root, file),
os.path.join('pype', '..')))
)
progress += pype_inc
self._progress_callback(int(progress))
pype_root = include_dir.parent.resolve()
# generate list of filtered paths
dir_filter = [pype_root / f for f in self.pype_filter]
file: Path
for file in pype_list:
progress += pype_inc
self._progress_callback(int(progress))
# if file resides in filtered path, skip it
is_inside = None
df: Path
for df in dir_filter:
try:
is_inside = file.resolve().relative_to(df)
except ValueError:
pass
if is_inside:
continue
processed_path = file
self._log.debug(f"processing {processed_path}")
self._print(f"- processing {processed_path}", False)
zip_file.write(file,
"pype" / file.relative_to(pype_root))
# test if zip is ok
zip_file.testzip()
self._progress_callback(100)
@ -342,10 +463,15 @@ class BootstrapRepos:
This will enable Python to import modules is second-level directories
in zip file.
Adding to both `sys.path` and `PYTHONPATH`, skipping duplicates.
Args:
archive (str): path to archive.
archive (Path): path to archive.
"""
if not archive.is_file() and not archive.exists():
raise ValueError("Archive is not file.")
with ZipFile(archive, "r") as zip_file:
name_list = zip_file.namelist()
@ -362,8 +488,41 @@ class BootstrapRepos:
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
@staticmethod
def add_paths_from_directory(directory: Path) -> None:
"""Add first level directories as paths to :mod:`sys.path`.
This works the same as :meth:`add_paths_from_archive` but in
specified directory.
Adding to both `sys.path` and `PYTHONPATH`, skipping duplicates.
Args:
directory (Path): path to directory.
"""
if not directory.exists() and not directory.is_dir():
raise ValueError("directory is invalid")
roots = []
for item in directory.iterdir():
if item.is_dir():
root = item.as_posix()
if root not in roots:
roots.append(root)
sys.path.insert(0, root)
pythonpath = os.getenv("PYTHONPATH", "")
paths = pythonpath.split(os.pathsep)
paths += roots
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
def find_pype(
self, pype_path: Path = None) -> Union[List[PypeVersion], None]:
self,
pype_path: Path = None,
staging: bool = False,
include_zips: bool = False) -> Union[List[PypeVersion], None]:
"""Get ordered dict of detected Pype version.
Resolution order for Pype is following:
@ -374,6 +533,10 @@ class BootstrapRepos:
Args:
pype_path (Path, optional): Try to find Pype on the given path.
staging (bool, optional): Filter only staging version, skip them
otherwise.
include_zips (bool, optional): If set True it will try to find
Pype in zip files in given directory.
Returns:
dict of Path: Dictionary of detected Pype version.
@ -383,42 +546,110 @@ class BootstrapRepos:
"""
dir_to_search = self.data_dir
if os.getenv("PYPE_PATH"):
if Path(os.getenv("PYPE_PATH")).exists():
dir_to_search = Path(os.getenv("PYPE_PATH"))
else:
try:
registry_dir = Path(self.registry.get_item("pypePath"))
if registry_dir.exists():
dir_to_search = registry_dir
except ValueError:
# nothing found in registry, we'll use data dir
pass
# if we have pyp_path specified, search only there.
# if we have pype_path specified, search only there.
if pype_path:
dir_to_search = pype_path
else:
if os.getenv("PYPE_PATH"):
if Path(os.getenv("PYPE_PATH")).exists():
dir_to_search = Path(os.getenv("PYPE_PATH"))
else:
try:
registry_dir = Path(
str(self.registry.get_item("pypePath")))
if registry_dir.exists():
dir_to_search = registry_dir
except ValueError:
# nothing found in registry, we'll use data dir
pass
# pype installation dir doesn't exists
if not dir_to_search.exists():
return None
_pype_versions = []
file_pattern = re.compile(r"^pype-repositories-v(?P<version>\d+\.\d+\.\d*.+?).zip$") # noqa: E501
# iterate over directory in first level and find all that might
# contain Pype.
for file in dir_to_search.iterdir():
m = re.match(
file_pattern,
file.name)
if m:
try:
_pype_versions.append(
PypeVersion(
version=m.group("version"), path=file))
except ValueError:
# cannot parse version string
print(m)
pass
# if file, strip extension, in case of dir not.
name = file.name if file.is_dir() else file.stem
result = PypeVersion.version_in_str(name)
if result[0]:
detected_version: PypeVersion
detected_version = result[1]
if file.is_dir():
# if item is directory that might (based on it's name)
# contain Pype version, check if it really does contain
# Pype and that their versions matches.
try:
# add one 'pype' level as inside dir there should
# be many other repositories.
version_str = BootstrapRepos.get_version(
file / "pype")
version_check = PypeVersion(version=version_str)
except ValueError:
self._log.error(
f"cannot determine version from {file}")
continue
version_main = version_check.get_main_version()
detected_main = detected_version.get_main_version()
if version_main != detected_main:
self._log.error(
(f"dir version ({detected_version}) and "
f"its content version ({version_check}) "
"doesn't match. Skipping."))
continue
if file.is_file():
if not include_zips:
continue
# skip non-zip files
if file.suffix.lower() != ".zip":
continue
# open zip file, look inside and parse version from Pype
# inside it. If there is none, or it is different from
# version specified in file name, skip it.
try:
with ZipFile(file, "r") as zip_file:
with zip_file.open(
"pype/pype/version.py") as version_file:
zip_version = {}
exec(version_file.read(), zip_version)
version_check = PypeVersion(
version=zip_version["__version__"])
version_main = version_check.get_main_version() # noqa: E501
detected_main = detected_version.get_main_version() # noqa: E501
if version_main != detected_main:
self._log.error(
(f"zip version ({detected_version}) "
f"and its content version "
f"({version_check}) "
"doesn't match. Skipping."))
continue
except BadZipFile:
self._log.error(f"{file} is not zip file")
continue
except KeyError:
self._log.error("Zip not containing Pype")
continue
detected_version.path = file
if staging and detected_version.is_staging():
_pype_versions.append(detected_version)
if not staging and not detected_version.is_staging():
_pype_versions.append(detected_version)
return sorted(_pype_versions)
@ -426,16 +657,16 @@ class BootstrapRepos:
def _get_pype_from_mongo(mongo_url: str) -> Union[Path, None]:
"""Get path from Mongo database.
This sets environment variable ``AVALON_MONGO`` for
This sets environment variable ``PYPE_MONGO`` for
:mod:`pype.settings` to be able to read data from database.
It will then retrieve environment variables and among them
must be ``PYPE_ROOT``.
must be ``PYPE_PATH``.
Args:
mongo_url (str): mongodb connection url
Returns:
Path: if path from ``PYPE_ROOT`` is found.
Path: if path from ``PYPE_PATH`` is found.
None: if not.
"""
@ -479,11 +710,18 @@ class BootstrapRepos:
self._log.error(f"{pype_path} doesn't exists.")
return None
# find pype zip files in location. In that location, there can be
# either "live" Pype repository, or multiple zip files.
# test if entered path isn't user data dir
if self.data_dir == pype_path:
self._log.error("cannot point to user data dir")
return None
# find pype zip files in location. There can be
# either "live" Pype repository, or multiple zip files or even
# multiple pype version directories. This process looks into zip
# files and directories and tries to parse `version.py` file.
versions = self.find_pype(pype_path)
if versions:
self._log.info(f"found Pype zips in [ {pype_path} ].")
self._log.info(f"found Pype in [ {pype_path} ]")
self._log.info(f"latest version found is [ {versions[-1]} ]")
destination = self.data_dir / versions[-1].path.name
@ -503,13 +741,43 @@ class BootstrapRepos:
if not destination.parent.exists():
destination.parent.mkdir(parents=True)
# latest version found is directory
if versions[-1].path.is_dir():
# zip it, copy it and extract it
# create zip inside temporary directory.
self._log.info("Creating zip from directory ...")
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-v{versions[-1]}.zip"
self._log.info(f"creating zip: {temp_zip}")
self._create_pype_zip(temp_zip, versions[-1].path)
if not os.path.exists(temp_zip):
self._log.error("make archive failed.")
return None
destination = self.data_dir / temp_zip.name
elif versions[-1].path.is_file():
# in this place, it must be zip file as `find_pype()` is
# checking just that.
assert versions[-1].path.suffix.lower() == ".zip", (
"Invalid file format"
)
try:
self._log.info("Copying zip to destination ...")
copyfile(versions[-1].path.as_posix(), destination.as_posix())
except OSError:
self._log.error(
"cannot copy detected version to user data directory",
exc_info=True)
return None
# extract zip there
self._log.info("extracting zip to destination ...")
with ZipFile(versions[-1].path, "r") as zip_ref:
zip_ref.extractall(destination)
return destination
# if we got here, it means that location is "live" Pype repository.
@ -518,4 +786,171 @@ class BootstrapRepos:
if not repo_file.exists():
self._log.error(f"installing zip {repo_file} failed.")
return None
return repo_file
destination = self.data_dir / repo_file.stem
if destination.exists():
try:
destination.unlink()
except OSError:
self._log.error(
f"cannot remove already existing {destination}",
exc_info=True)
return None
destination.mkdir(parents=True)
# extract zip there
self._log.info("extracting zip to destination ...")
with ZipFile(versions[-1].path, "r") as zip_ref:
zip_ref.extractall(destination)
return destination
def _print(self, message, error=False):
if self._message:
self._message.emit(message, error)
def extract_pype(self, version: PypeVersion) -> Union[Path, None]:
"""Extract zipped Pype version to user data directory.
Args:
version (PypeVersion): Version of Pype.
Returns:
Path: path to extracted version.
None: if something failed.
"""
if not version.path:
raise ValueError(
f"version {version} is not associated with any file")
destination = self.data_dir / version.path.stem
if destination.exists():
try:
destination.unlink()
except OSError as e:
msg = f"!!! Cannot remove already existing {destination}"
self._log.error(msg)
self._log.error(e.strerror)
self._print(msg, True)
self._print(e.strerror, True)
return None
destination.mkdir(parents=True)
# extract zip there
self._print("Extracting zip to destination ...")
with ZipFile(version.path, "r") as zip_ref:
zip_ref.extractall(destination)
self._print(f"Installed as {version.path.stem}")
return destination
def install_version(self, pype_version: PypeVersion, force: bool = False):
"""Install Pype version to user data directory.
Args:
pype_version (PypeVersion): Pype version to install.
force (bool, optional): Force overwrite existing version.
Returns:
Path: Path to installed Pype.
Raises:
PypeVersionExists: If not forced and this version already exist
in user data directory.
PypeVersionInvalid: If version to install is invalid.
PypeVersionIOError: If copying or zipping fail.
"""
# test if version is located (in user data dir)
is_inside = False
try:
is_inside = pype_version.path.resolve().relative_to(
self.data_dir)
except ValueError:
# if relative path cannot be calculated, Pype version is not
# inside user data dir
pass
if is_inside:
raise PypeVersionExists("Pype already inside user data dir")
# determine destination directory name
# for zip file strip suffix
destination = self.data_dir / pype_version.path.stem
# test if destination file already exist, if so lets delete it.
# we consider path on location as authoritative place.
if destination.exists() and force:
try:
destination.unlink()
except OSError:
self._log.error(
f"cannot remove already existing {destination}",
exc_info=True)
return None
else:
raise PypeVersionExists(f"{destination} already exist.")
# create destination parent directories even if they don't exist.
if not destination.exists():
destination.mkdir(parents=True)
# version is directory
if pype_version.path.is_dir():
# create zip inside temporary directory.
self._log.info("Creating zip from directory ...")
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-v{pype_version}.zip"
self._log.info(f"creating zip: {temp_zip}")
self._create_pype_zip(temp_zip, pype_version.path)
if not os.path.exists(temp_zip):
self._log.error("make archive failed.")
raise PypeVersionIOError("Zip creation failed.")
# set zip as version source
pype_version.path = temp_zip
elif pype_version.path.is_file():
# check if file is zip (by extension)
if pype_version.path.suffix.lower() != ".zip":
raise PypeVersionInvalid("Invalid file format")
try:
# copy file to destination
self._log.info("Copying zip to destination ...")
copyfile(pype_version.path.as_posix(), destination.as_posix())
except OSError as e:
self._log.error(
"cannot copy version to user data directory",
exc_info=True)
raise PypeVersionIOError(
"can't copy version to destination") from e
# extract zip there
self._log.info("extracting zip to destination ...")
with ZipFile(pype_version.path, "r") as zip_ref:
zip_ref.extractall(destination)
return destination
class PypeVersionExists(Exception):
"""Exception for handling existing Pype version."""
pass
class PypeVersionInvalid(Exception):
"""Exception for handling invalid Pype version."""
pass
class PypeVersionIOError(Exception):
"""Exception for handling IO errors in Pype version."""
pass

View file

@ -19,7 +19,7 @@ class InstallDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(InstallDialog, self).__init__(parent)
self._mongo_url = ""
self._mongo_url = os.getenv("PYPE_MONGO", "")
self.setWindowTitle("Pype - Configure Pype repository path")
self._icon_path = os.path.join(
@ -149,10 +149,13 @@ class InstallDialog(QtWidgets.QDialog):
self.setLayout(mongo_layout)
def _mongo_changed(self, mongo: str):
self.parent()._mongo_url = mongo
self.parent().mongo_url = mongo
def get_mongo_url(self):
return self.parent()._mongo_url
return self.parent().mongo_url
def set_mongo_url(self, mongo: str):
self._mongo_input.setText(mongo)
def set_valid(self):
self._mongo_input.setStyleSheet(
@ -175,6 +178,8 @@ class InstallDialog(QtWidgets.QDialog):
)
self._mongo = MongoWidget(self)
if self._mongo_url:
self._mongo.set_mongo_url(self._mongo_url)
# Bottom button bar
# --------------------------------------------------------------------
@ -303,14 +308,17 @@ class InstallDialog(QtWidgets.QDialog):
options |= QtWidgets.QFileDialog.DontUseNativeDialog
options |= QtWidgets.QFileDialog.ShowDirsOnly
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
result = QtWidgets.QFileDialog.getExistingDirectory(
parent=self,
caption='Select path',
directory=os.getcwd(),
options=options)
if filename:
filename = QtCore.QDir.toNativeSeparators(filename)
if not result:
return
filename = result[0]
filename = QtCore.QDir.toNativeSeparators(filename)
if os.path.isdir(filename):
self.user_input.setText(filename)
@ -378,6 +386,7 @@ class InstallDialog(QtWidgets.QDialog):
if len(self._path) < 1:
self._mongo.setVisible(False)
return path
def _update_console(self, msg: str, error: bool = False) -> None:
"""Display message in console.

View file

@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
"""Working thread for installer."""
import os
import sys
from zipfile import ZipFile
from Qt.QtCore import QThread, Signal
from .bootstrap_repos import BootstrapRepos
from .bootstrap_repos import PypeVersion
from .tools import validate_mongo_connection
@ -43,8 +46,9 @@ class InstallThread(QThread):
self.message.emit("Installing Pype ...", False)
# find local version of Pype
bs = BootstrapRepos(progress_callback=self.set_progress)
local_version = bs.get_local_version()
bs = BootstrapRepos(
progress_callback=self.set_progress, message=self.message)
local_version = bs.get_local_live_version()
# if user did entered nothing, we install Pype from local version.
# zip content of `repos`, copy it to user data dir and append
@ -68,14 +72,71 @@ class InstallThread(QThread):
os.environ["PYPE_MONGO"] = self._mongo
self.message.emit(
f"Detecting installed Pype versions in {bs.data_dir}", False)
detected = bs.find_pype(include_zips=True)
if detected:
if PypeVersion(version=local_version) < detected[-1]:
self.message.emit((
f"Latest installed version {detected[-1]} is newer "
f"then currently running {local_version}"
), False)
self.message.emit("Skipping Pype install ...", False)
if detected[-1].path.suffix.lower() == ".zip":
bs.extract_pype(detected[-1])
return
if PypeVersion(version=local_version) == detected[-1]:
self.message.emit((
f"Latest installed version is the same as "
f"currently running {local_version}"
), False)
self.message.emit("Skipping Pype install ...", False)
return
self.message.emit((
"All installed versions are older then "
f"currently running one {local_version}"
), False)
else:
# we cannot build install package from frozen code.
if getattr(sys, 'frozen', False):
self.message.emit("None detected.", True)
self.message.emit(("Please set path to Pype sources to "
"build installation."), False)
return
else:
self.message.emit("None detected.", False)
self.message.emit(
f"We will use local Pype version {local_version}", False)
repo_file = bs.install_live_repos()
if not repo_file:
self.message.emit(
f"!!! install failed - {repo_file}", True)
f"!!! Install failed - {repo_file}", True)
return
self.message.emit(f"installed as {repo_file}", False)
destination = bs.data_dir / repo_file.stem
if destination.exists():
try:
destination.unlink()
except OSError as e:
self.message.emit(
f"!!! Cannot remove already existing {destination}",
True)
self.message.emit(e.strerror, True)
return
destination.mkdir(parents=True)
# extract zip there
self.message.emit("Extracting zip to destination ...", False)
with ZipFile(repo_file, "r") as zip_ref:
zip_ref.extractall(destination)
self.message.emit(f"Installed as {repo_file}", False)
else:
# if we have mongo connection string, validate it, set it to
# user settings and get PYPE_PATH from there.
@ -87,10 +148,14 @@ class InstallThread(QThread):
bs.registry.set_secure_item("pypeMongo", self._mongo)
os.environ["PYPE_MONGO"] = self._mongo
if os.getenv("PYPE_PATH") == self._path:
...
self.message.emit(f"processing {self._path}", True)
repo_file = bs.process_entered_location(self._path)
if not repo_file:
self.message.emit(f"!!! Cannot install", True)
self.message.emit("!!! Cannot install", True)
return
def set_path(self, path: str) -> None:

BIN
igniter/pype.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

413
igniter/splash.txt Normal file
View file

@ -0,0 +1,413 @@
*
.*
*
.*
*
.
*
.*
*
.
.
*
.*
.*
.*
*
.
.
*
.*
.*
.*
*
.
_.
/**
\ *
\*
*
*
.
__.
---*
\ \*
\ *
\*
*
.
\___.
/* *
\ \ *
\ \*
\ *
\*
.
|____.
/* *
\|\ *
\ \ *
\ \ *
\ \*
\/.
_/_____.
/* *
/ \ *
\ \ *
\ \ *
\ \__*
\/__.
__________.
--*-- ___*
\ \ \/_*
\ \ __*
\ \ \_*
\ \____\*
\/____/.
\____________ .
/* ___ \*
\ \ \/_\ *
\ \ _____*
\ \ \___/*
\ \____\ *
\/____/ .
|___________ .
/* ___ \ *
\|\ \/_\ \ *
\ \ _____/ *
\ \ \___/ *
\ \____\ / *
\/____/ \.
_/__________ .
/* ___ \ *
/ \ \/_\ \ *
\ \ _____/ *
\ \ \___/ ---*
\ \____\ / \__*
\/____/ \/__.
____________ .
--*-- ___ \ *
\ \ \/_\ \ *
\ \ _____/ *
\ \ \___/ ---- *
\ \____\ / \____\*
\/____/ \/____/.
____________
/\ ___ \ .
\ \ \/_\ \ *
\ \ _____/ *
\ \ \___/ ---- *
\ \____\ / \____\ .
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \ .
\ \ _____/ *
\ \ \___/ ---- *
\ \____\ / \____\ .
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ .
\ \ \___/ ---- *
\ \____\ / \____\ .
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/
\ \ \___/ ---- *
\ \____\ / \____\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/
\ \ \___/ ---- .
\ \____\ / \____\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ _
\ \ \___/ ----
\ \____\ / \____\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ----
\ \____\ / \____\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ---- \
\ \____\ / \____\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ---- \
\ \____\ / \____\ \
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ---- \
\ \____\ / \____\ __\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ---- \
\ \____\ / \____\ \__\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ---- \ \
\ \____\ / \____\ \__\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___
\ \ \___/ ---- \ \
\ \____\ / \____\ \__\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___.
\ \ \___/ ---- \ \\
\ \____\ / \____\ \__\,
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ .
\ \ \___/ ---- \ \\
\ \____\ / \____\ \__\\,
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ _.
\ \ \___/ ---- \ \\\
\ \____\ / \____\ \__\\\
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ __.
\ \ \___/ ---- \ \\ \
\ \____\ / \____\ \__\\_/.
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___.
\ \ \___/ ---- \ \\ \\
\ \____\ / \____\ \__\\__\.
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ .
\ \ \___/ ---- \ \\ \\
\ \____\ / \____\ \__\\__\\.
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ _.
\ \ \___/ ---- \ \\ \\\
\ \____\ / \____\ \__\\__\\.
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ __.
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\_.
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ __.
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__.
\/____/ \/____/
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ .
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ *
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ O*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ .oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ ..oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . .oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . p.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . Py.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYp.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPe.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE .oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE c.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE C1.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE ClU.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE CluB.oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE Club .oO*
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE Club . ..
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE Club . ..
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE Club . .
____________
/\ ___ \
\ \ \/_\ \
\ \ _____/ ___ ___ ___
\ \ \___/ ---- \ \\ \\ \
\ \____\ / \____\ \__\\__\\__\
\/____/ \/____/ . PYPE Club .

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
"""Pype terminal animation."""
import blessed
from pathlib import Path
from time import sleep
NO_TERMINAL = False
try:
term = blessed.Terminal()
except AttributeError:
# this happens when blessed cannot find proper terminal.
# If so, skip printing ascii art animation.
NO_TERMINAL = True
def play_animation():
"""Play ASCII art Pype animation."""
if NO_TERMINAL:
return
print(term.home + term.clear)
frame_size = 7
splash_file = Path(__file__).parent / "splash.txt"
with splash_file.open("r") as sf:
animation = sf.readlines()
animation_length = int(len(animation) / frame_size)
current_frame = 0
for _ in range(animation_length):
frame = "".join(
scanline
for y, scanline in enumerate(
animation[current_frame : current_frame + frame_size]
)
)
with term.location(0, 0):
# term.aquamarine3_bold(frame)
print(f"{term.bold}{term.aquamarine3}{frame}{term.normal}")
sleep(0.02)
current_frame += frame_size
print(term.move_y(7))

View file

@ -1,14 +1,104 @@
# -*- coding: utf-8 -*-
"""Tools used in **Igniter** GUI."""
"""Tools used in **Igniter** GUI.
Functions ``compose_url()`` and ``decompose_url()`` are the same as in
``pype.lib`` and they are here to avoid importing pype module before its
version is decided.
"""
import os
import sys
import uuid
from urllib.parse import urlparse
from typing import Dict
from urllib.parse import urlparse, parse_qs
from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError, InvalidURI
from pype.lib import decompose_url, compose_url
def decompose_url(url: str) -> Dict:
"""Decompose mongodb url to its separate components.
Args:
url (str): Mongodb url.
Returns:
dict: Dictionary of components.
"""
components = {
"scheme": None,
"host": None,
"port": None,
"username": None,
"password": None,
"auth_db": None
}
result = urlparse(url)
if result.scheme is None:
_url = "mongodb://{}".format(url)
result = urlparse(_url)
components["scheme"] = result.scheme
components["host"] = result.hostname
try:
components["port"] = result.port
except ValueError:
raise RuntimeError("invalid port specified")
components["username"] = result.username
components["password"] = result.password
try:
components["auth_db"] = parse_qs(result.query)['authSource'][0]
except KeyError:
# no auth db provided, mongo will use the one we are connecting to
pass
return components
def compose_url(scheme: str = None,
host: str = None,
username: str = None,
password: str = None,
port: int = None,
auth_db: str = None) -> str:
"""Compose mongodb url from its individual components.
Args:
scheme (str, optional):
host (str, optional):
username (str, optional):
password (str, optional):
port (str, optional):
auth_db (str, optional):
Returns:
str: mongodb url
"""
url = "{scheme}://"
if username and password:
url += "{username}:{password}@"
url += "{host}"
if port:
url += ":{port}"
if auth_db:
url += "?authSource={auth_db}"
return url.format(**{
"scheme": scheme,
"host": host,
"username": username,
"password": password,
"port": port,
"auth_db": auth_db
})
def validate_mongo_connection(cnx: str) -> (bool, str):
@ -22,30 +112,29 @@ def validate_mongo_connection(cnx: str) -> (bool, str):
"""
parsed = urlparse(cnx)
if parsed.scheme in ["mongodb", "mongodb+srv"]:
# we have mongo connection string. Let's try if we can connect.
components = decompose_url(cnx)
mongo_args = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 1000
}
port = components.get("port")
if port is not None:
mongo_args["port"] = int(port)
try:
client = MongoClient(**mongo_args)
client.server_info()
except ServerSelectionTimeoutError as e:
return False, f"Cannot connect to server {cnx} - {e}"
except ValueError:
return False, f"Invalid port specified {parsed.port}"
except InvalidURI as e:
return False, str(e)
else:
return True, "Connection is successful"
else:
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
return False, "Not mongodb schema"
# we have mongo connection string. Let's try if we can connect.
components = decompose_url(cnx)
mongo_args = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 1000
}
port = components.get("port")
if port is not None:
mongo_args["port"] = int(port)
try:
client = MongoClient(**mongo_args)
client.server_info()
except ServerSelectionTimeoutError as e:
return False, f"Cannot connect to server {cnx} - {e}"
except ValueError:
return False, f"Invalid port specified {parsed.port}"
except InvalidURI as e:
return False, str(e)
else:
return True, "Connection is successful"
def validate_path_string(path: str) -> (bool, str):
@ -81,26 +170,6 @@ def validate_path_string(path: str) -> (bool, str):
return False, "Not implemented yet"
def add_acre_to_sys_path():
"""Add full path of acre module to sys.path on ignitation."""
try:
# Skip if is possible to import
import acre
except ImportError:
# Full path to acred repository related to current file
acre_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"repos",
"acre"
)
# Add path to sys.path
sys.path.append(acre_dir)
# Validate that acre can be imported
import acre
def load_environments(sections: list = None) -> dict:
"""Load environments from Pype.
@ -114,7 +183,6 @@ def load_environments(sections: list = None) -> dict:
dict of str: loaded and processed environments.
"""
add_acre_to_sys_path()
import acre
from pype import settings
@ -131,5 +199,4 @@ def load_environments(sections: list = None) -> dict:
continue
merged_env = acre.append(merged_env, parsed_env)
env = acre.compute(merged_env, cleanup=True)
return env
return acre.compute(merged_env, cleanup=True)

466
igniter/user_settings.py Normal file
View file

@ -0,0 +1,466 @@
# -*- coding: utf-8 -*-
"""Package to deal with saving and retrieving user specific settings."""
import os
from datetime import datetime
from abc import ABCMeta, abstractmethod
import json
# disable lru cache in Python 2
try:
from functools import lru_cache
except ImportError:
def lru_cache(maxsize):
def max_size(func):
def wrapper(*args, **kwargs):
value = func(*args, **kwargs)
return value
return wrapper
return max_size
# ConfigParser was renamed in python3 to configparser
try:
import configparser
except ImportError:
import ConfigParser as configparser
import platform
import appdirs
import six
@six.add_metaclass(ABCMeta)
class ASettingRegistry():
"""Abstract class defining structure of **SettingRegistry** class.
It is implementing methods to store secure items into keyring, otherwise
mechanism for storing common items must be implemented in abstract
methods.
Attributes:
_name (str): Registry names.
"""
def __init__(self, name):
# 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 = {}
def set_item(self, name, value):
# type: (str, str) -> None
"""Set item to settings registry.
Args:
name (str): Name of the item.
value (str): Value of the item.
"""
self._set_item(name, value)
@abstractmethod
def _set_item(self, name, value):
# type: (str, str) -> None
# Implement it
pass
def __setitem__(self, name, value):
self._items[name] = value
self._set_item(name, value)
def get_item(self, name):
# type: (str) -> str
"""Get item from settings registry.
Args:
name (str): Name of the item.
Returns:
value (str): Value of the item.
Raises:
ValueError: If item doesn't exist.
"""
return self._get_item(name)
@abstractmethod
def _get_item(self, name):
# type: (str) -> str
# Implement it
pass
def __getitem__(self, name):
return self._get_item(name)
def delete_item(self, name):
# type: (str) -> None
"""Delete item from settings registry.
Args:
name (str): Name of the item.
"""
self._delete_item(name)
@abstractmethod
def _delete_item(self, name):
# type: (str) -> None
"""Delete item from settings.
Note:
see :meth:`pype.lib.user_settings.ARegistrySettings.delete_item`
"""
pass
def __delitem__(self, name):
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`.
This class is using :mod:`configparser` (ini) files to store items.
"""
def __init__(self, name, path):
# type: (str, str) -> IniSettingRegistry
super(IniSettingRegistry, self).__init__(name)
# get registry file
version = os.getenv("PYPE_VERSION", "N/A")
self._registry_file = os.path.join(path, "{}.ini".format(name))
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)
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
print("# {}".format(now), cfg)
def set_item_section(
self, section, name, value):
# type: (str, str, str) -> None
"""Set item to specific section of ini registry.
If section doesn't exists, it is created.
Args:
section (str): Name of section.
name (str): Name of the item.
value (str): Value of the item.
"""
value = str(value)
config = configparser.ConfigParser()
config.read(self._registry_file)
if not config.has_section(section):
config.add_section(section)
current = config[section]
current[name] = value
with open(self._registry_file, mode="w") as cfg:
config.write(cfg)
def _set_item(self, name, value):
# type: (str, str) -> None
self.set_item_section("MAIN", name, value)
def set_item(self, name, value):
# type: (str, str) -> None
"""Set item to settings ini file.
This saves item to ``DEFAULT`` section of ini as each item there
must reside in some section.
Args:
name (str): Name of the item.
value (str): Value of the item.
"""
# this does the some, overridden just for different docstring.
# we cast value to str as ini options values must be strings.
super(IniSettingRegistry, self).set_item(name, str(value))
def get_item(self, name):
# type: (str) -> str
"""Gets item from settings ini file.
This gets settings from ``DEFAULT`` section of ini file as each item
there must reside in some section.
Args:
name (str): Name of the item.
Returns:
str: Value of item.
Raises:
ValueError: If value doesn't exist.
"""
return super(IniSettingRegistry, self).get_item(name)
@lru_cache(maxsize=32)
def get_item_from_section(self, section, name):
# type: (str, str) -> str
"""Get item from section of ini file.
This will read ini file and try to get item value from specified
section. If that section or item doesn't exist, :exc:`ValueError`
is risen.
Args:
section (str): Name of ini section.
name (str): Name of the item.
Returns:
str: Item value.
Raises:
ValueError: If value doesn't exist.
"""
config = configparser.ConfigParser()
config.read(self._registry_file)
try:
value = config[section][name]
except KeyError:
raise ValueError(
"Registry doesn't contain value {}:{}".format(section, name))
return value
def _get_item(self, name):
# type: (str) -> str
return self.get_item_from_section("MAIN", name)
def delete_item_from_section(self, section, name):
# type: (str, str) -> None
"""Delete item from section in ini file.
Args:
section (str): Section name.
name (str): Name of the item.
Raises:
ValueError: If item doesn't exist.
"""
self.get_item_from_section.cache_clear()
config = configparser.ConfigParser()
config.read(self._registry_file)
try:
_ = config[section][name]
except KeyError:
raise ValueError(
"Registry doesn't contain value {}:{}".format(section, name))
config.remove_option(section, name)
# if section is empty, delete it
if len(config[section].keys()) == 0:
config.remove_section(section)
with open(self._registry_file, mode="w") as cfg:
config.write(cfg)
def _delete_item(self, name):
"""Delete item from default section.
Note:
See :meth:`~pype.lib.IniSettingsRegistry.delete_item_from_section`
"""
self.delete_item_from_section("MAIN", name)
class JSONSettingRegistry(ASettingRegistry):
"""Class using json file as storage."""
def __init__(self, name, path):
# type: (str, str) -> JSONSettingRegistry
super(JSONSettingRegistry, self).__init__(name)
#: str: name of registry file
self._registry_file = os.path.join(path, "{}.json".format(name))
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
header = {
"__metadata__": {
"pype-version": os.getenv("PYPE_VERSION", "N/A"),
"generated": now
},
"registry": {}
}
if not os.path.exists(os.path.dirname(self._registry_file)):
os.makedirs(os.path.dirname(self._registry_file), exist_ok=True)
if not os.path.exists(self._registry_file):
with open(self._registry_file, mode="w") as cfg:
json.dump(header, cfg, indent=4)
@lru_cache(maxsize=32)
def _get_item(self, name):
# type: (str) -> object
"""Get item value from registry json.
Note:
See :meth:`pype.lib.JSONSettingRegistry.get_item`
"""
with open(self._registry_file, mode="r") as cfg:
data = json.load(cfg)
try:
value = data["registry"][name]
except KeyError:
raise ValueError(
"Registry doesn't contain value {}".format(name))
return value
def get_item(self, name):
# type: (str) -> object
"""Get item value from registry json.
Args:
name (str): Name of the item.
Returns:
value of the item
Raises:
ValueError: If item is not found in registry file.
"""
return self._get_item(name)
def _set_item(self, name, value):
# type: (str, object) -> None
"""Set item value to registry json.
Note:
See :meth:`pype.lib.JSONSettingRegistry.set_item`
"""
with open(self._registry_file, "r+") as cfg:
data = json.load(cfg)
data["registry"][name] = value
cfg.truncate(0)
cfg.seek(0)
json.dump(data, cfg, indent=4)
def set_item(self, name, value):
# type: (str, object) -> None
"""Set item and its value into json registry file.
Args:
name (str): name of the item.
value (Any): value of the item.
"""
self._set_item(name, value)
def _delete_item(self, name):
# type: (str) -> None
self._get_item.cache_clear()
with open(self._registry_file, "r+") as cfg:
data = json.load(cfg)
del data["registry"][name]
cfg.truncate(0)
cfg.seek(0)
json.dump(data, cfg, indent=4)
class PypeSettingsRegistry(JSONSettingRegistry):
"""Class handling Pype general settings registry.
Attributes:
vendor (str): Name used for path construction.
product (str): Additional name used for path construction.
"""
def __init__(self):
self.vendor = "pypeclub"
self.product = "pype"
path = appdirs.user_data_dir(self.product, self.vendor)
super(PypeSettingsRegistry, self).__init__("pype_settings", path)

268
pype.py
View file

@ -1,268 +0,0 @@
# -*- coding: utf-8 -*-
"""Main entry point for Pype command.
Bootstrapping process of Pype is as follows:
`PYPE_PATH` is checked for existence - either one from environment or
from user settings. Precedence takes the one set by environment.
On this path we try to find zip files with `pype-repositories-v3.x.x.zip`
format.
If no Pype repositories are found in `PYPE_PATH (user data dir)
then **Igniter** (Pype setup tool) will launch its GUI.
It can be used to specify `PYPE_PATH` or if it is _not_ specified, current
*"live"* repositories will be used to create such zip file and copy it to
appdata dir in user home. Version will be determined by version specified
in Pype module.
If Pype repositories zip file is found in default install location
(user data dir) or in `PYPE_PATH`, it will get list of those zips there and
use latest one or the one specified with optional `--use-version` command
line argument. If the one specified doesn't exist then latest available
version will be used. All repositories in that zip will be added
to `sys.path` and `PYTHONPATH`.
If Pype is live (not frozen) then current version of Pype module will be
used. All directories under `repos` will be added to `sys.path` and
`PYTHONPATH`.
Pype depends on connection to `MongoDB`_. You can specify MongoDB connection
string via `AVALON_MONGO` set in environment or it can be set in user
settings or via **Igniter** GUI.
Todo:
Move or remove bootstrapping environments out of the code.
.. _MongoDB:
https://www.mongodb.com/
"""
import os
import re
import sys
import traceback
from igniter.tools import load_environments, add_acre_to_sys_path
from igniter import BootstrapRepos
try:
import acre
except ImportError:
add_acre_to_sys_path()
import acre
def set_environments() -> None:
"""Set loaded environments.
.. todo:
better handling of environments
"""
# FIXME: remove everything except global
env = load_environments(["global"])
env = acre.merge(env, dict(os.environ))
os.environ.clear()
os.environ.update(env)
def set_modules_environments():
"""Set global environments for pype's modules.
This requires to have pype in `sys.path`.
"""
from pype.modules import ModulesManager
modules_manager = ModulesManager()
module_envs = modules_manager.collect_global_environments()
publish_plugin_dirs = modules_manager.collect_plugin_paths()["publish"]
# Set pyblish plugins paths if any module want to register them
if publish_plugin_dirs:
publish_paths_str = os.environ.get("PYBLISHPLUGINPATH") or ""
publish_paths = publish_paths_str.split(os.pathsep)
_publish_paths = set()
for path in publish_paths:
if path:
_publish_paths.add(os.path.normpath(path))
for path in publish_plugin_dirs:
_publish_paths.add(os.path.normpath(path))
module_envs["PYBLISHPLUGINPATH"] = os.pathsep.join(_publish_paths)
# Metge environments with current environments and update values
if module_envs:
parsed_envs = acre.parse(module_envs)
env = acre.merge(parsed_envs, dict(os.environ))
os.environ.clear()
os.environ.update(env)
def boot():
"""Bootstrap Pype."""
from pype.lib.terminal_splash import play_animation
play_animation()
# find pype versions
bootstrap = BootstrapRepos()
pype_versions = bootstrap.find_pype()
# check for `--use-version=3.0.0` argument.
use_version = None
for arg in sys.argv:
m = re.search(r"--use-version=(?P<version>\d+\.\d+\.\d*.+?)", arg)
if m and m.group('version'):
use_version = m.group('version')
break
if not os.getenv("PYPE_MONGO"):
try:
pype_mongo = bootstrap.registry.get_secure_item("pypeMongo")
except ValueError:
print("*** No DB connection string specified.")
print("--- launching setup UI ...")
import igniter
igniter.run()
return
else:
os.environ["PYPE_MONGO"] = pype_mongo
set_environments()
if getattr(sys, 'frozen', False):
if not pype_versions:
import igniter
igniter.run()
version_path = BootstrapRepos.get_version_path_from_list(
use_version, pype_versions)
if version_path:
# use specified
bootstrap.add_paths_from_archive(version_path)
else:
if use_version is not None:
print(("!!! Specified version was not found, using "
"latest available"))
# use latest
version_path = pype_versions[-1].path
bootstrap.add_paths_from_archive(version_path)
use_version = str(pype_versions[-1])
os.environ["PYPE_ROOT"] = version_path.as_posix()
else:
# run through repos and add them to sys.path and PYTHONPATH
pype_root = os.path.dirname(os.path.realpath(__file__))
local_version = bootstrap.get_local_version()
if use_version and use_version != local_version:
version_path = BootstrapRepos.get_version_path_from_list(
use_version, pype_versions)
if version_path:
# use specified
bootstrap.add_paths_from_archive(version_path)
os.environ["PYPE_ROOT"] = pype_root
repos = os.listdir(os.path.join(pype_root, "repos"))
repos = [os.path.join(pype_root, "repos", repo) for repo in repos]
# add self to python paths
repos.insert(0, pype_root)
for repo in repos:
sys.path.append(repo)
pythonpath = os.getenv("PYTHONPATH", "")
paths = pythonpath.split(os.pathsep)
paths += repos
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
# DEPRECATED: remove when `pype-config` dissolves into Pype for good.
# .-=-----------------------=-=. ^ .=-=--------------------------=-.
os.environ["PYPE_MODULE_ROOT"] = os.environ["PYPE_ROOT"]
# delete Pype module from cache so it is used from specific version
try:
del sys.modules["pype"]
del sys.modules["pype.version"]
except AttributeError:
pass
from pype import cli
from pype.lib import terminal as t
from pype.version import __version__
print(">>> loading environments ...")
set_environments()
set_modules_environments()
info = get_info()
info.insert(0, ">>> Using Pype from [ {} ]".format(
os.path.dirname(cli.__file__)))
info_length = len(max(info, key=len))
info.insert(0, f"*** Pype [{__version__}] " + "-" * info_length)
for i in info:
t.echo(i)
try:
cli.main(obj={}, prog_name="pype")
except Exception:
exc_info = sys.exc_info()
print("!!! Pype crashed:")
traceback.print_exception(*exc_info)
sys.exit(1)
def get_info() -> list:
"""Print additional information to console."""
from pype.lib.mongo import get_default_components
from pype.lib.log import PypeLogger
components = get_default_components()
infos = []
if not getattr(sys, 'frozen', False):
infos.append(("Pype variant", "staging"))
else:
infos.append(("Pype variant", "production"))
infos.append(("Running pype from", os.environ.get('PYPE_ROOT')))
infos.append(("Using mongodb", components["host"]))
if os.environ.get("FTRACK_SERVER"):
infos.append(("Using FTrack at",
os.environ.get("FTRACK_SERVER")))
if os.environ.get('DEADLINE_REST_URL'):
infos.append(("Using Deadline webservice at",
os.environ.get("DEADLINE_REST_URL")))
if os.environ.get('MUSTER_REST_URL'):
infos.append(("Using Muster at",
os.environ.get("MUSTER_REST_URL")))
# Reinitialize
PypeLogger.initialize()
log_components = PypeLogger.log_mongo_url_components
if log_components["host"]:
infos.append(("Logging to MongoDB", log_components["host"]))
infos.append((" - port", log_components["port"] or "<N/A>"))
infos.append((" - database", PypeLogger.log_database_name))
infos.append((" - collection", PypeLogger.log_collection_name))
infos.append((" - user", log_components["username"] or "<N/A>"))
if log_components["auth_db"]:
infos.append((" - auth source", log_components["auth_db"]))
maximum = max([len(i[0]) for i in infos])
formatted = []
for info in infos:
padding = (maximum - len(info[0])) + 1
formatted.append(
"... {}:{}[ {} ]".format(info[0], " " * padding, info[1]))
return formatted
if __name__ == "__main__":
boot()

View file

@ -26,6 +26,14 @@ from .lib.mongo import (
get_default_components
)
from .lib.applications import (
ApplicationManager
)
from .lib.avalon_context import (
BuildWorkfile
)
from . import resources
from .plugin import (
@ -63,6 +71,8 @@ __all__ = [
"decompose_url",
"compose_url",
"get_default_components",
"ApplicationManager",
"BuildWorkfile",
# Resources
"resources",

View file

@ -1,13 +1,20 @@
# -*- coding: utf-8 -*-
"""Package for handling pype command line arguments."""
import os
import sys
import click
# import sys
from .pype_commands import PypeCommands
import click
@click.group(invoke_without_command=True)
@click.pass_context
@click.option("--use-version",
expose_value=False, help="use specified version")
@click.option("--use-staging", is_flag=True,
expose_value=False, help="use staging variants")
def main(ctx):
"""Pype is main command serving as entry point to pipeline system.
@ -20,6 +27,7 @@ def main(ctx):
@main.command()
@click.option("-d", "--dev", is_flag=True, help="Settings in Dev mode")
def settings(dev=False):
"""Show Pype Settings UI."""
PypeCommands().launch_settings_gui(dev)
@ -38,12 +46,6 @@ def tray(debug=False):
PypeCommands().launch_tray(debug)
@main.command()
def mongodb():
"""Launch local mongodb server. Useful for development."""
PypeCommands().launch_local_mongodb()
@main.command()
@click.option("-d", "--debug", is_flag=True, help="Print debug messages")
@click.option("--ftrack-url", envvar="FTRACK_SERVER",
@ -56,7 +58,7 @@ def mongodb():
envvar="FTRACK_EVENTS_PATH",
help=("path to ftrack event handlers"))
@click.option("--no-stored-credentials", is_flag=True,
help="dont use stored credentials")
help="don't use stored credentials")
@click.option("--store-credentials", is_flag=True,
help="store provided credentials")
@click.option("--legacy", is_flag=True,
@ -165,41 +167,6 @@ def texturecopy(debug, project, asset, path):
PypeCommands().texture_copy(project, asset, path)
@main.command()
@click.option("-k", "--keyword", help="select tests by keyword to run",
type=click.STRING)
@click.argument("id", nargs=-1, type=click.STRING)
def test(pype, keyword, id):
"""Run test suite."""
if pype:
PypeCommands().run_pype_tests(keyword, id)
@main.command()
def make_docs():
"""Generate documentation with Sphinx into `docs/build`."""
PypeCommands().make_docs()
@main.command()
def coverage():
"""Generate code coverage report."""
PypeCommands().pype_setup_coverage()
@main.command()
def clean():
"""Delete python bytecode files.
Working throughout Pype directory, it will remove all pyc bytecode files.
This is normally not needed but there are cases when update of repostories
caused errors thanks to these files. If you encounter errors complaining
about `magic number`, run this command.
"""
# TODO: reimplement in Python
pass
@main.command(context_settings={"ignore_unknown_options": True})
@click.option("--app", help="Registered application name")
@click.option("--project", help="Project name",
@ -233,7 +200,9 @@ def launch(app, project, asset, task,
Optionally you can specify ftrack credentials if needed.
ARGUMENTS are passed to launched application.
"""
# TODO: this needs to switch for Settings
if ftrack_server:
os.environ["FTRACK_SERVER"] = ftrack_server
@ -255,6 +224,33 @@ def launch(app, project, asset, task,
@main.command()
def validate_config():
"""Validate all json configuration files for errors."""
PypeCommands().validate_jsons()
@click.option("-p", "--path", help="Path to zip file", default=None)
def generate_zip(path):
"""Generate Pype zip from current sources.
If PATH is not provided, it will create zip file in user data dir.
"""
PypeCommands().generate_zip(path)
@main.command(
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True))
@click.argument("script", required=True, type=click.Path(exists=True))
def run(script):
"""Run python script in Pype context."""
import runpy
if not script:
print("Error: missing path to script file.")
else:
args = sys.argv
args.remove("run")
args.remove(script)
sys.argv = args
args_string = " ".join(args[1:])
print(f"... running: {script} {args_string}")
runpy.run_path(script, run_name="__main__", )

View file

@ -26,7 +26,7 @@ class HarmonyPrelaunchHook(PreLaunchHook):
(
"import avalon.harmony;"
"avalon.harmony.launch(\"{}\")"
).format(harmony_executable)
).format(harmony_executable.replace("\\", "/"))
]
# Append as whole list as these areguments should not be separated

View file

@ -0,0 +1,131 @@
import os
import pype.lib
from pype.api import Logger, Anatomy
import shutil
import getpass
import avalon.api
class PhotoshopPrelaunch(pype.lib.PypeHook):
"""This hook will check for the existence of PyWin
PyWin is a requirement for the Photoshop integration.
"""
project_code = None
host_name = "photoshop"
def __init__(self, logger=None):
if not logger:
self.log = Logger().get_logger(self.__class__.__name__)
else:
self.log = logger
self.signature = "( {} )".format(self.__class__.__name__)
def execute(self, *args, env: dict = None) -> bool:
output = pype.lib._subprocess(["pip", "install", "pywin32==227"])
self.log.info(output)
workfile_path = self.get_workfile_plath(env, self.host_name)
# adding compulsory environment var for openting file
env["PYPE_WORKFILE_PATH"] = workfile_path
return True
def get_anatomy_filled(self, workdir, project_name, asset_name,
task_name, host_name, extension):
dbcon = avalon.api.AvalonMongoDB()
dbcon.install()
dbcon.Session["AVALON_PROJECT"] = project_name
project_document = dbcon.find_one({"type": "project"})
asset_document = dbcon.find_one({
"type": "asset",
"name": asset_name
})
dbcon.uninstall()
asset_doc_parents = asset_document["data"].get("parents")
hierarchy = "/".join(asset_doc_parents)
data = {
"project": {
"name": project_document["name"],
"code": project_document["data"].get("code")
},
"task": task_name,
"asset": asset_name,
"app": host_name,
"hierarchy": hierarchy
}
anatomy = Anatomy(project_name)
file_template = anatomy.templates["work"]["file"]
data.update({
"version": 1,
"user": os.environ.get("PYPE_USERNAME") or getpass.getuser(),
"ext": extension
})
return avalon.api.last_workfile(
workdir, file_template, data,
avalon.api.HOST_WORKFILE_EXTENSIONS[host_name], True
)
def get_workfile_plath(self, env, host_name):
# get context variables
project_name = env["AVALON_PROJECT"]
asset_name = env["AVALON_ASSET"]
task_name = env["AVALON_TASK"]
workdir = env["AVALON_WORKDIR"]
extension = avalon.api.HOST_WORKFILE_EXTENSIONS[host_name][0]
template_env_key = "{}_TEMPLATE".format(host_name.upper())
# get workfile path
workfile_path = self.get_anatomy_filled(
workdir, project_name, asset_name, task_name, host_name, extension)
# create workdir if doesn't exist
os.makedirs(workdir, exist_ok=True)
self.log.info("Work dir is: `{}`".format(workdir))
# get last version of workfile
workfile_last = env.get("AVALON_LAST_WORKFILE")
self.log.debug("_ workfile_last: `{}`".format(workfile_last))
if workfile_last:
workfile = workfile_last
workfile_path = os.path.join(workdir, workfile)
# copy workfile from template if doesnt exist any on path
if not os.path.isfile(workfile_path):
# try to get path from environment or use default
# from `pype.hosts.<host_name>` dir
template_path = env.get(template_env_key) or os.path.join(
env.get("PYPE_MODULE_ROOT"),
"pype/hosts/{}/template{}".format(host_name, extension)
)
# try to get template from project config folder
proj_config_path = os.path.join(
env["PYPE_PROJECT_CONFIGS"], project_name)
if os.path.exists(proj_config_path):
template_file = None
for f in os.listdir(proj_config_path):
if extension in os.path.splitext(f):
template_file = f
if template_file:
template_path = os.path.join(
proj_config_path, template_file)
self.log.info(
"Creating workfile from template: `{}`".format(template_path))
# copy template to new destinantion
shutil.copy2(
os.path.normpath(template_path),
os.path.normpath(workfile_path)
)
self.log.info("Workfile to open: `{}`".format(workfile_path))
return workfile_path

View file

@ -9,7 +9,7 @@ import avalon.tools.sceneinventory
import pyblish.api
from pype import lib
from pype.api import get_current_project_settings
from pype.api import (get_current_project_settings)
def set_scene_settings(settings):
@ -48,20 +48,13 @@ def get_asset_settings():
"resolutionWidth": resolution_width,
"resolutionHeight": resolution_height
}
settings = get_current_project_settings()
try:
skip_resolution_check = (
get_current_project_settings()
["harmony"]
["general"]
["skip_resolution_check"]
)
skip_timelines_check = (
get_current_project_settings()
["harmony"]
["general"]
["skip_timelines_check"]
)
skip_resolution_check = \
settings["harmony"]["general"]["skip_resolution_check"]
skip_timelines_check = \
settings["harmony"]["general"]["skip_timelines_check"]
except KeyError:
skip_resolution_check = []
skip_timelines_check = []

View file

@ -5,7 +5,7 @@
var LD_OPENHARMONY_PATH = System.getenv('LIB_OPENHARMONY_PATH');
include(LD_OPENHARMONY_PATH + '/openHarmony.js');
this.__proto__['$'] = $;
/**
@ -79,7 +79,8 @@ PypeHarmony.getSceneSettings = function() {
scene.getStopFrame(),
sound.getSoundtrackAll().path(),
scene.defaultResolutionX(),
scene.defaultResolutionY()
scene.defaultResolutionY(),
scene.defaultResolutionFOV()
];
};
@ -200,3 +201,16 @@ PypeHarmony.getDependencies = function(_node) {
}
return dependencies;
};
/**
* return version of running Harmony instance.
* @function
* @return {array} [major_version, minor_version]
*/
PypeHarmony.getVersion = function() {
return [
about.getMajorVersion(),
about.getMinorVersion()
];
};

View file

@ -0,0 +1,52 @@
/* global PypeHarmony:writable, include */
// ***************************************************************************
// * CollectFarmRender *
// ***************************************************************************
// check if PypeHarmony is defined and if not, load it.
if (typeof PypeHarmony !== 'undefined') {
var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS');
include(PYPE_HARMONY_JS + '/pype_harmony.js');
}
/**
* @namespace
* @classdesc Image Sequence loader JS code.
*/
var CollectFarmRender = function() {};
/**
* Get information important for render output.
* @function
* @param node {String} node name.
* @return {array} array of render info.
*
* @example
*
* var ret = [
* file_prefix, // like foo/bar-
* type, // PNG4, ...
* leading_zeros, // 3 - for 0001
* start // start frame
* ]
*/
CollectFarmRender.prototype.getRenderNodeSettings = function(n) {
// this will return
var output = [
node.getTextAttr(
n, frame.current(), 'DRAWING_NAME'),
node.getTextAttr(
n, frame.current(), 'DRAWING_TYPE'),
node.getTextAttr(
n, frame.current(), 'LEADING_ZEROS'),
node.getTextAttr(n, frame.current(), 'START')
];
return output;
};
// add self to Pype Loaders
PypeHarmony.Publish.CollectFarmRender = new CollectFarmRender();

View file

@ -1,144 +0,0 @@
import os
import sys
import logging
import nuke
from avalon import api as avalon
from avalon.tools import workfiles
from pyblish import api as pyblish
from pype.hosts.nuke import menu
from pype.api import Logger
from pype import PLUGINS_DIR
from . import lib
self = sys.modules[__name__]
self.workfiles_launched = False
log = Logger().get_logger(__name__)
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "nuke", "publish")
LOAD_PATH = os.path.join(PLUGINS_DIR, "nuke", "load")
CREATE_PATH = os.path.join(PLUGINS_DIR, "nuke", "create")
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nuke", "inventory")
# registering pyblish gui regarding settings in presets
if os.getenv("PYBLISH_GUI", None):
pyblish.register_gui(os.getenv("PYBLISH_GUI", None))
def reload_config():
"""Attempt to reload pipeline at run-time.
CAUTION: This is primarily for development and debugging purposes.
"""
import importlib
for module in (
"{}.api".format(AVALON_CONFIG),
"{}.hosts.nuke.actions".format(AVALON_CONFIG),
"{}.hosts.nuke.presets".format(AVALON_CONFIG),
"{}.hosts.nuke.menu".format(AVALON_CONFIG),
"{}.hosts.nuke.plugin".format(AVALON_CONFIG),
"{}.hosts.nuke.lib".format(AVALON_CONFIG),
):
log.info("Reloading module: {}...".format(module))
module = importlib.import_module(module)
try:
importlib.reload(module)
except AttributeError as e:
log.warning("Cannot reload module: {}".format(e))
reload(module)
def install():
''' Installing all requarements for Nuke host
'''
log.info("Registering Nuke plug-ins..")
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# Register Avalon event for workfiles loading.
avalon.on("workio.open_file", lib.check_inventory_versions)
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
workfile_settings = lib.WorkfileSettings()
# Disable all families except for the ones we explicitly want to see
family_states = [
"write",
"review",
"nukenodes"
"gizmo"
]
avalon.data["familiesStateDefault"] = False
avalon.data["familiesStateToggled"] = family_states
# Workfiles.
launch_workfiles = os.environ.get("WORKFILES_STARTUP")
if launch_workfiles:
nuke.addOnCreate(launch_workfiles_app, nodeClass="Root")
# Set context settings.
nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root")
nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root")
menu.install()
def launch_workfiles_app():
'''Function letting start workfiles after start of host
'''
if not self.workfiles_launched:
self.workfiles_launched = True
workfiles.show(os.environ["AVALON_WORKDIR"])
def uninstall():
'''Uninstalling host's integration
'''
log.info("Deregistering Nuke plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
reload_config()
menu.uninstall()
def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle node passthrough states on instance toggles."""
log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
instance, old_value, new_value))
from avalon.nuke import (
viewer_update_and_undo_stop,
add_publish_knob
)
# Whether instances should be passthrough based on new value
with viewer_update_and_undo_stop():
n = instance[0]
try:
n["publish"].value()
except ValueError:
n = add_publish_knob(n)
log.info(" `Publish` knob was added to write node..")
n["publish"].setValue(new_value)

View file

@ -0,0 +1,141 @@
import os
import sys
import nuke
from avalon import api as avalon
from avalon.tools import workfiles
from pyblish import api as pyblish
from pype.api import Logger
import pype.hosts.nuke
from . import lib, menu
self = sys.modules[__name__]
self.workfiles_launched = False
log = Logger().get_logger(__name__)
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.nuke.__file__))
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
# registering pyblish gui regarding settings in presets
if os.getenv("PYBLISH_GUI", None):
pyblish.register_gui(os.getenv("PYBLISH_GUI", None))
def reload_config():
"""Attempt to reload pipeline at run-time.
CAUTION: This is primarily for development and debugging purposes.
"""
import importlib
for module in (
"{}.api".format(AVALON_CONFIG),
"{}.hosts.nuke.api.actions".format(AVALON_CONFIG),
"{}.hosts.nuke.api.menu".format(AVALON_CONFIG),
"{}.hosts.nuke.api.plugin".format(AVALON_CONFIG),
"{}.hosts.nuke.api.lib".format(AVALON_CONFIG),
):
log.info("Reloading module: {}...".format(module))
module = importlib.import_module(module)
try:
importlib.reload(module)
except AttributeError as e:
from importlib import reload
log.warning("Cannot reload module: {}".format(e))
reload(module)
def install():
''' Installing all requarements for Nuke host
'''
log.info("Registering Nuke plug-ins..")
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# Register Avalon event for workfiles loading.
avalon.on("workio.open_file", lib.check_inventory_versions)
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
workfile_settings = lib.WorkfileSettings()
# Disable all families except for the ones we explicitly want to see
family_states = [
"write",
"review",
"nukenodes"
"gizmo"
]
avalon.data["familiesStateDefault"] = False
avalon.data["familiesStateToggled"] = family_states
# Workfiles.
launch_workfiles = os.environ.get("WORKFILES_STARTUP")
if launch_workfiles:
nuke.addOnCreate(launch_workfiles_app, nodeClass="Root")
# Set context settings.
nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root")
# nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root")
menu.install()
def launch_workfiles_app():
'''Function letting start workfiles after start of host
'''
if not self.workfiles_launched:
self.workfiles_launched = True
workfiles.show(os.environ["AVALON_WORKDIR"])
def uninstall():
'''Uninstalling host's integration
'''
log.info("Deregistering Nuke plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
reload_config()
menu.uninstall()
def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle node passthrough states on instance toggles."""
log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
instance, old_value, new_value))
from avalon.nuke import (
viewer_update_and_undo_stop,
add_publish_knob
)
# Whether instances should be passthrough based on new value
with viewer_update_and_undo_stop():
n = instance[0]
try:
n["publish"].value()
except ValueError:
n = add_publish_knob(n)
log.info(" `Publish` knob was added to write node..")
n["publish"].setValue(new_value)

View file

@ -5,7 +5,7 @@ from avalon.nuke.lib import (
select_nodes
)
from ...action import get_errored_instances_from_context
from pype.api import get_errored_instances_from_context
class SelectInvalidAction(pyblish.api.Action):

View file

@ -6,26 +6,74 @@ from collections import OrderedDict
from avalon import api, io, lib
import avalon.nuke
from avalon.nuke import lib as anlib
import pype.api as pype
from pype.api import (
Logger,
Anatomy,
get_version_from_path,
get_anatomy_settings,
get_hierarchy,
get_asset,
config,
ApplicationManager
)
import nuke
from .presets import (
get_colorspace_preset,
get_node_dataflow_preset,
get_node_colorspace_preset,
get_anatomy
)
from .utils import set_context_favorites
log = pype.Logger().get_logger(__name__)
log = Logger().get_logger(__name__)
self = sys.modules[__name__]
self._project = None
def get_node_imageio_setting(**kwarg):
''' Get preset data for dataflow (fileType, compression, bitDepth)
'''
log.info(kwarg)
host = str(kwarg.get("host", "nuke"))
nodeclass = kwarg.get("nodeclass", None)
creator = kwarg.get("creator", None)
project_name = os.getenv("AVALON_PROJECT")
assert any([host, nodeclass]), nuke.message(
"`{}`: Missing mandatory kwargs `host`, `cls`".format(__file__))
imageio_nodes = (get_anatomy_settings(project_name)
["imageio"]
.get(host, None)
["nodes"]
["requiredNodes"]
)
for node in imageio_nodes:
log.info(node)
if node["nukeNodeClass"] == nodeclass:
if creator in node["plugins"]:
imageio_node = node
log.info("ImageIO node: {}".format(imageio_node))
return imageio_node
def get_imageio_input_colorspace(filename):
''' Get input file colorspace based on regex in settings.
'''
imageio_regex_inputs = (get_anatomy_settings(os.getenv("AVALON_PROJECT"))
["imageio"]
["nuke"]
["regexInputs"]
["inputs"]
)
preset_clrsp = None
for regexInput in imageio_regex_inputs:
if bool(re.search(regexInput["regex"], filename)):
preset_clrsp = str(regexInput["colorspace"])
return preset_clrsp
def on_script_load():
''' Callback for ffmpeg support
'''
@ -39,7 +87,7 @@ def on_script_load():
def check_inventory_versions():
"""
Actiual version idetifier of Loaded containers
Actual version idetifier of Loaded containers
Any time this function is run it will check all nodes and filter only
Loader nodes for its version. It will get all versions from database
@ -52,9 +100,9 @@ def check_inventory_versions():
container = avalon.nuke.parse_container(each)
if container:
node = container["_node"]
avalon_knob_data = avalon.nuke.get_avalon_knob_data(
node, ['avalon:', 'ak:'])
node = nuke.toNode(container["objectName"])
avalon_knob_data = avalon.nuke.read(
node)
# get representation from io
representation = io.find_one({
@ -88,7 +136,7 @@ def writes_version_sync():
''' Callback synchronizing version of publishable write nodes
'''
try:
rootVersion = pype.get_version_from_path(nuke.root().name())
rootVersion = get_version_from_path(nuke.root().name())
padding = len(rootVersion)
new_version = "v" + str("{" + ":0>{}".format(padding) + "}").format(
int(rootVersion)
@ -103,8 +151,8 @@ def writes_version_sync():
if "AvalonTab" not in each.knobs():
continue
avalon_knob_data = avalon.nuke.get_avalon_knob_data(
each, ['avalon:', 'ak:'])
avalon_knob_data = avalon.nuke.read(
each)
try:
if avalon_knob_data['families'] not in ["render"]:
@ -113,7 +161,7 @@ def writes_version_sync():
node_file = each['file'].value()
node_version = "v" + pype.get_version_from_path(node_file)
node_version = "v" + get_version_from_path(node_file)
log.debug("node_version: {}".format(node_version))
node_new_file = node_file.replace(node_version, new_version)
@ -134,24 +182,40 @@ def version_up_script():
nukescripts.script_and_write_nodes_version_up()
def check_subsetname_exists(nodes, subset_name):
"""
Checking if node is not already created to secure there is no duplicity
Arguments:
nodes (list): list of nuke.Node objects
subset_name (str): name we try to find
Returns:
bool: True of False
"""
result = next((True for n in nodes
if subset_name in avalon.nuke.read(n).get("subset", "")), False)
return result
def get_render_path(node):
''' Generate Render path from presets regarding avalon knob data
'''
data = dict()
data['avalon'] = avalon.nuke.get_avalon_knob_data(
node, ['avalon:', 'ak:'])
data['avalon'] = avalon.nuke.read(
node)
data_preset = {
"class": data['avalon']['family'],
"preset": data['avalon']['families']
}
nuke_dataflow_writes = get_node_dataflow_preset(**data_preset)
nuke_colorspace_writes = get_node_colorspace_preset(**data_preset)
nuke_imageio_writes = get_node_imageio_setting(**data_preset)
application = lib.get_application(os.environ["AVALON_APP_NAME"])
data.update({
"nuke_dataflow_writes": nuke_dataflow_writes,
"nuke_colorspace_writes": nuke_colorspace_writes
"application": application,
"nuke_imageio_writes": nuke_imageio_writes
})
anatomy_filled = format_anatomy(data)
@ -169,7 +233,7 @@ def format_anatomy(data):
'''
# TODO: perhaps should be nonPublic
anatomy = get_anatomy()
anatomy = Anatomy()
log.debug("__ anatomy.templates: {}".format(anatomy.templates))
try:
@ -192,18 +256,16 @@ def format_anatomy(data):
version = data.get("version", None)
if not version:
file = script_name()
data["version"] = pype.get_version_from_path(file)
data["version"] = get_version_from_path(file)
project_document = io.find_one({"type": "project"})
data.update({
"subset": data["avalon"]["subset"],
"asset": data["avalon"]["asset"],
"task": api.Session["AVALON_TASK"],
"task": os.environ["AVALON_TASK"],
"family": data["avalon"]["family"],
"project": {"name": project_document["name"],
"code": project_document["data"].get("code", '')},
"representation": data["nuke_dataflow_writes"]["file_type"],
"app": api.Session["AVALON_APP"],
"hierarchy": pype.get_hierarchy(),
"hierarchy": get_hierarchy(),
"frame": "#" * padding,
})
return anatomy.format(data)
@ -217,11 +279,11 @@ def script_name():
def add_button_write_to_read(node):
name = "createReadNode"
label = "[ Create Read ]"
label = "Cread Read From Rendered"
value = "import write_to_read;write_to_read.write_to_read(nuke.thisNode())"
k = nuke.PyScript_Knob(name, label, value)
k.setFlag(0x1000)
node.addKnob(k)
knob = nuke.PyScript_Knob(name, label, value)
knob.clearFlag(nuke.STARTLINE)
node.addKnob(knob)
def create_write_node(name, data, input=None, prenodes=None, review=True):
@ -254,18 +316,26 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
node (obj): group node with avalon data as Knobs
'''
nuke_dataflow_writes = get_node_dataflow_preset(**data)
nuke_colorspace_writes = get_node_colorspace_preset(**data)
imageio_writes = get_node_imageio_setting(**data)
app_manager = ApplicationManager()
app_name = os.environ.get("AVALON_APP_NAME")
if app_name:
app = app_manager.applications.get(app_name)
for knob in imageio_writes["knobs"]:
if knob["name"] == "file_type":
representation = knob["value"]
try:
data.update({
"nuke_dataflow_writes": nuke_dataflow_writes,
"nuke_colorspace_writes": nuke_colorspace_writes
"app": app.host_name,
"imageio_writes": imageio_writes,
"representation": representation,
})
anatomy_filled = format_anatomy(data)
except Exception as e:
msg = "problem with resolving anatomy tepmlate: {}".format(e)
msg = "problem with resolving anatomy template: {}".format(e)
log.error(msg)
nuke.message(msg)
@ -274,7 +344,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
fpath = data["fpath_template"].format(
work=fpath, version=data["version"], subset=data["subset"],
frame=data["frame"],
ext=data["nuke_dataflow_writes"]["file_type"]
ext=representation
)
# create directory
@ -287,17 +357,12 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
})
# adding dataflow template
log.debug("nuke_dataflow_writes: `{}`".format(nuke_dataflow_writes))
{_data.update({k: v})
for k, v in nuke_dataflow_writes.items()
if k not in ["_id", "_previous"]}
log.debug("imageio_writes: `{}`".format(imageio_writes))
for knob in imageio_writes["knobs"]:
if knob["name"] not in ["_id", "_previous"]:
_data.update({knob["name"]: knob["value"]})
# adding colorspace template
log.debug("nuke_colorspace_writes: `{}`".format(nuke_colorspace_writes))
{_data.update({k: v})
for k, v in nuke_colorspace_writes.items()}
_data = avalon.nuke.lib.fix_data_for_node_create(_data)
_data = anlib.fix_data_for_node_create(_data)
log.debug("_data: `{}`".format(_data))
@ -366,7 +431,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
prev_node = now_node
# creating write node
write_node = now_node = avalon.nuke.lib.add_write_node(
write_node = now_node = anlib.add_write_node(
"inside_{}".format(name),
**_data
)
@ -383,30 +448,40 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
now_node.setInput(0, prev_node)
# imprinting group node
avalon.nuke.imprint(GN, data["avalon"])
# add divider
GN.addKnob(nuke.Text_Knob(''))
anlib.set_avalon_knob_data(GN, data["avalon"])
anlib.add_publish_knob(GN)
add_rendering_knobs(GN)
if review:
add_review_knob(GN)
# add divider
GN.addKnob(nuke.Text_Knob(''))
GN.addKnob(nuke.Text_Knob('', 'Rendering'))
# Add linked knobs.
linked_knob_names = ["Render", "use_limit", "first", "last"]
linked_knob_names = [
"_grp-start_",
"use_limit", "first", "last",
"_grp-end_",
"Render"
]
for name in linked_knob_names:
link = nuke.Link_Knob(name)
link.makeLink(write_node.name(), name)
link.setName(name)
link.setFlag(0x1000)
GN.addKnob(link)
# add divider
GN.addKnob(nuke.Text_Knob(''))
if "_grp-start_" in name:
knob = nuke.Tab_Knob(
"rnd_attr", "Rendering attributes", nuke.TABBEGINCLOSEDGROUP)
GN.addKnob(knob)
elif "_grp-end_" in name:
knob = nuke.Tab_Knob(
"rnd_attr_end", "Rendering attributes", nuke.TABENDGROUP)
GN.addKnob(knob)
else:
link = nuke.Link_Knob("")
link.makeLink(write_node.name(), name)
link.setName(name)
if "Render" in name:
link.setLabel("Render Local")
link.setFlag(0x1000)
GN.addKnob(link)
# adding write to read button
add_button_write_to_read(GN)
@ -431,9 +506,9 @@ def add_rendering_knobs(node):
node (obj): with added knobs
'''
if "render" not in node.knobs():
knob = nuke.Enumeration_Knob("render", "Render", [
knob = nuke.Enumeration_Knob("render", "", [
"Use existing frames", "Local", "On farm"])
knob.setFlag(0x1000)
knob.clearFlag(nuke.STARTLINE)
node.addKnob(knob)
return node
@ -538,7 +613,7 @@ class WorkfileSettings(object):
self._project = kwargs.get(
"project") or io.find_one({"type": "project"})
self._asset = kwargs.get("asset_name") or api.Session["AVALON_ASSET"]
self._asset_entity = pype.get_asset(self._asset)
self._asset_entity = get_asset(self._asset)
self._root_node = root_node or nuke.root()
self._nodes = self.get_nodes(nodes=nodes)
@ -674,7 +749,7 @@ class WorkfileSettings(object):
log.error(msg)
return
from avalon.nuke import get_avalon_knob_data
from avalon.nuke import read
for node in nuke.allNodes():
@ -682,7 +757,7 @@ class WorkfileSettings(object):
continue
# get data from avalon knob
avalon_knob_data = get_avalon_knob_data(node, ["avalon:", "ak:"])
avalon_knob_data = read(node)
if not avalon_knob_data:
continue
@ -730,7 +805,7 @@ class WorkfileSettings(object):
continue
# load nuke presets for Read's colorspace
read_clrs_presets = get_colorspace_preset().get(
read_clrs_presets = config.get_init_presets()["colorspace"].get(
"nuke", {}).get("read", {})
# check if any colorspace presets for read is mathing
@ -771,7 +846,8 @@ class WorkfileSettings(object):
def set_colorspace(self):
''' Setting colorpace following presets
'''
nuke_colorspace = get_colorspace_preset().get("nuke", None)
nuke_colorspace = config.get_init_presets(
)["colorspace"].get("nuke", None)
try:
self.set_root_colorspace(nuke_colorspace["root"])
@ -836,7 +912,7 @@ class WorkfileSettings(object):
handle_start = data["handleStart"]
handle_end = data["handleEnd"]
fps = data["fps"]
fps = float(data["fps"])
frame_start = int(data["frameStart"]) - handle_start
frame_end = int(data["frameEnd"]) + handle_end
@ -863,7 +939,7 @@ class WorkfileSettings(object):
node['frame_range_lock'].setValue(True)
# adding handle_start/end to root avalon knob
if not avalon.nuke.imprint(self._root_node, {
if not anlib.set_avalon_knob_data(self._root_node, {
"handleStart": int(handle_start),
"handleEnd": int(handle_end)
}):
@ -971,7 +1047,7 @@ class WorkfileSettings(object):
# replace reset resolution from avalon core to pype's
self.reset_frame_range_handles()
# add colorspace menu item
self.set_colorspace()
# self.set_colorspace()
def set_favorites(self):
work_dir = os.getenv("AVALON_WORKDIR")
@ -1031,8 +1107,8 @@ def get_write_node_template_attr(node):
'''
# get avalon data from node
data = dict()
data['avalon'] = avalon.nuke.get_avalon_knob_data(
node, ['avalon:', 'ak:'])
data['avalon'] = avalon.nuke.read(
node)
data_preset = {
"class": data['avalon']['family'],
"families": data['avalon']['families'],
@ -1040,25 +1116,20 @@ def get_write_node_template_attr(node):
}
# get template data
nuke_dataflow_writes = get_node_dataflow_preset(**data_preset)
nuke_colorspace_writes = get_node_colorspace_preset(**data_preset)
nuke_imageio_writes = get_node_imageio_setting(**data_preset)
# collecting correct data
correct_data = OrderedDict({
"file": get_render_path(node)
})
# adding dataflow template
# adding imageio template
{correct_data.update({k: v})
for k, v in nuke_dataflow_writes.items()
for k, v in nuke_imageio_writes.items()
if k not in ["_id", "_previous"]}
# adding colorspace template
{correct_data.update({k: v})
for k, v in nuke_colorspace_writes.items()}
# fix badly encoded data
return avalon.nuke.lib.fix_data_for_node_create(correct_data)
return anlib.fix_data_for_node_create(correct_data)
class ExporterReview:
@ -1177,6 +1248,7 @@ class ExporterReviewLut(ExporterReview):
"""
def __init__(self,
klass,
instance,
@ -1279,6 +1351,7 @@ class ExporterReviewMov(ExporterReview):
instance (pyblish.instance): instance of pyblish context
"""
def __init__(self,
klass,
instance,
@ -1298,6 +1371,7 @@ class ExporterReviewMov(ExporterReview):
self.viewer_lut_raw = klass.viewer_lut_raw
self.bake_colorspace_fallback = klass.bake_colorspace_fallback
self.bake_colorspace_main = klass.bake_colorspace_main
self.write_colorspace = instance.data["colorspace"]
self.name = name or "baked"
self.ext = ext or "mov"
@ -1343,6 +1417,8 @@ class ExporterReviewMov(ExporterReview):
r_node["origfirst"].setValue(self.first_frame)
r_node["last"].setValue(self.last_frame)
r_node["origlast"].setValue(self.last_frame)
r_node["colorspace"].setValue(self.write_colorspace)
# connect
self._temp_nodes.append(r_node)
self.previous_node = r_node

View file

@ -1,11 +1,8 @@
import os
import nuke
from avalon.api import Session
from pype.hosts.nuke import lib
from ...lib import BuildWorkfile
from pype.api import Logger
from pype.tools import workfiles
from .lib import WorkfileSettings
from pype.api import Logger, BuildWorkfile
log = Logger().get_logger(__name__)
@ -13,25 +10,6 @@ log = Logger().get_logger(__name__)
def install():
menubar = nuke.menu("Nuke")
menu = menubar.findItem(Session["AVALON_LABEL"])
workfile_settings = lib.WorkfileSettings
# replace reset resolution from avalon core to pype's
name = "Work Files..."
rm_item = [
(i, item) for i, item in enumerate(menu.items()) if name in item.name()
][0]
log.debug("Changing Item: {}".format(rm_item))
menu.removeItem(rm_item[1].name())
menu.addCommand(
name,
lambda: workfiles.show(
os.environ["AVALON_WORKDIR"]
),
index=(rm_item[0])
)
# replace reset resolution from avalon core to pype's
name = "Reset Resolution"
new_name = "Set Resolution"
@ -44,7 +22,7 @@ def install():
menu.removeItem(rm_item[1].name())
menu.addCommand(
new_name,
lambda: workfile_settings().reset_resolution(),
lambda: WorkfileSettings().reset_resolution(),
index=(rm_item[0])
)
@ -59,14 +37,14 @@ def install():
menu.removeItem(rm_item[1].name())
menu.addCommand(
new_name,
lambda: workfile_settings().reset_frame_range_handles(),
lambda: WorkfileSettings().reset_frame_range_handles(),
index=(rm_item[0])
)
# add colorspace menu item
name = "Set Colorspace"
menu.addCommand(
name, lambda: workfile_settings().set_colorspace(),
name, lambda: WorkfileSettings().set_colorspace(),
index=(rm_item[0] + 2)
)
log.debug("Adding menu item: {}".format(name))
@ -83,7 +61,7 @@ def install():
name = "Apply All Settings"
menu.addCommand(
name,
lambda: workfile_settings().set_context_settings(),
lambda: WorkfileSettings().set_context_settings(),
index=(rm_item[0] + 3)
)
log.debug("Adding menu item: {}".format(name))

View file

@ -0,0 +1,23 @@
import avalon.api
import avalon.nuke
from pype.api import get_current_project_settings
from .lib import check_subsetname_exists
import nuke
class PypeCreator(avalon.nuke.pipeline.Creator):
"""Pype Nuke Creator class wrapper
"""
def __init__(self, *args, **kwargs):
super(PypeCreator, self).__init__(*args, **kwargs)
self.presets = get_current_project_settings()["nuke"]["create"].get(
self.__class__.__name__, {}
)
if check_subsetname_exists(
nuke.allNodes(),
self.data["subset"]):
msg = ("The subset name `{0}` is already used on a node in"
"this workfile.".format(self.data["subset"]))
self.log.error(msg + '\n\nPlease use other subset name!')
raise NameError("`{0}: {1}".format(__name__, msg))
return

View file

@ -4,12 +4,13 @@ from avalon.nuke import lib as anlib
from pype.api import resources
def set_context_favorites(favorites={}):
def set_context_favorites(favorites=None):
""" Addig favorite folders to nuke's browser
Argumets:
favorites (dict): couples of {name:path}
"""
favorites = favorites or {}
icon_path = resources.get_resource("icons", "folder-favorite3.png")
for name, path in favorites.items():
nuke.addFavoriteDir(

View file

@ -1,13 +0,0 @@
import re
import avalon.api
import avalon.nuke
from pype.api import get_current_project_settings
class PypeCreator(avalon.nuke.pipeline.Creator):
"""Pype Nuke Creator class wrapper
"""
def __init__(self, *args, **kwargs):
super(PypeCreator, self).__init__(*args, **kwargs)
self.presets = get_current_project_settings()["nuke"]["create"].get(
self.__class__.__name__, {}
)

View file

@ -32,7 +32,7 @@ class CreateBackdrop(avalon.nuke.Creator):
bckd_node["note_font_size"].setValue(24)
bckd_node["label"].setValue("[{}]".format(self.name))
# add avalon knobs
instance = anlib.imprint(bckd_node, self.data)
instance = anlib.set_avalon_knob_data(bckd_node, self.data)
return instance
else:
@ -48,6 +48,6 @@ class CreateBackdrop(avalon.nuke.Creator):
bckd_node["note_font_size"].setValue(24)
bckd_node["label"].setValue("[{}]".format(self.name))
# add avalon knobs
instance = anlib.imprint(bckd_node, self.data)
instance = anlib.set_avalon_knob_data(bckd_node, self.data)
return instance

View file

@ -36,7 +36,7 @@ class CreateCamera(avalon.nuke.Creator):
# change node color
n["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
anlib.imprint(n, data)
anlib.set_avalon_knob_data(n, data)
return True
else:
msg = str("Please select nodes you "
@ -49,5 +49,5 @@ class CreateCamera(avalon.nuke.Creator):
camera_node = nuke.createNode("Camera2")
camera_node["tile_color"].setValue(int(self.node_color, 16))
# add avalon knobs
instance = anlib.imprint(camera_node, self.data)
instance = anlib.set_avalon_knob_data(camera_node, self.data)
return instance

View file

@ -34,7 +34,7 @@ class CreateGizmo(avalon.nuke.Creator):
if node.Class() in "Group":
node["name"].setValue("{}_GZM".format(self.name))
node["tile_color"].setValue(int(self.node_color, 16))
return anlib.imprint(node, self.data)
return anlib.set_avalon_knob_data(node, self.data)
else:
msg = ("Please select a group node "
"you wish to publish as the gizmo")
@ -57,7 +57,7 @@ class CreateGizmo(avalon.nuke.Creator):
"- create User knobs on the group")
# add avalon knobs
return anlib.imprint(gizmo_node, self.data)
return anlib.set_avalon_knob_data(gizmo_node, self.data)
else:
msg = ("Please select nodes you "
@ -80,4 +80,4 @@ class CreateGizmo(avalon.nuke.Creator):
"- create User knobs on the group")
# add avalon knobs
return anlib.imprint(gizmo_node, self.data)
return anlib.set_avalon_knob_data(gizmo_node, self.data)

View file

@ -44,7 +44,8 @@ class CrateRead(avalon.nuke.Creator):
continue
avalon_data = self.data
avalon_data['subset'] = "{}".format(self.name)
self.change_read_node(self.data["subset"], node, avalon_data)
avalon.nuke.lib.set_avalon_knob_data(node, avalon_data)
node['tile_color'].setValue(16744935)
count_reads += 1
if count_reads < 1:
@ -52,7 +53,3 @@ class CrateRead(avalon.nuke.Creator):
self.log.error(msg)
nuke.message(msg)
return
def change_read_node(self, name, node, data):
node = avalon.nuke.lib.imprint(node, data)
node['tile_color'].setValue(16744935)

View file

@ -1,7 +1,7 @@
from collections import OrderedDict
from pype.hosts.nuke import (
from pype.hosts.nuke.api import (
plugin,
lib as pnlib)
lib)
import nuke
@ -10,7 +10,7 @@ class CreateWritePrerender(plugin.PypeCreator):
name = "WritePrerender"
label = "Create Write Prerender"
hosts = ["nuke"]
n_class = "write"
n_class = "Write"
family = "prerender"
icon = "sign-out"
defaults = ["Key01", "Bg01", "Fg01", "Branch01", "Part01"]
@ -75,9 +75,10 @@ class CreateWritePrerender(plugin.PypeCreator):
# recreate new
write_data = {
"class": self.n_class,
"nodeclass": self.n_class,
"families": [self.family],
"avalon": self.data
"avalon": self.data,
"creator": self.__class__.__name__
}
if self.presets.get('fpath_template'):
@ -91,7 +92,9 @@ class CreateWritePrerender(plugin.PypeCreator):
"fpath_template": ("{work}/prerenders/nuke/{subset}"
"/{subset}.{frame}.{ext}")})
write_node = pnlib.create_write_node(
self.log.info("write_data: {}".format(write_data))
write_node = lib.create_write_node(
self.data["subset"],
write_data,
input=selected_node,

View file

@ -1,7 +1,7 @@
from collections import OrderedDict
from pype.hosts.nuke import (
from pype.hosts.nuke.api import (
plugin,
lib as pnlib)
lib)
import nuke
@ -10,7 +10,7 @@ class CreateWriteRender(plugin.PypeCreator):
name = "WriteRender"
label = "Create Write Render"
hosts = ["nuke"]
n_class = "write"
n_class = "Write"
family = "render"
icon = "sign-out"
defaults = ["Main", "Mask"]
@ -48,6 +48,7 @@ class CreateWriteRender(plugin.PypeCreator):
"or tick off `Use selection`")
self.log.error(msg)
nuke.message(msg)
return
if len(nodes) == 0:
msg = (
@ -56,6 +57,7 @@ class CreateWriteRender(plugin.PypeCreator):
)
self.log.error(msg)
nuke.message(msg)
return
selected_node = nodes[0]
inputs = [selected_node]
@ -76,9 +78,10 @@ class CreateWriteRender(plugin.PypeCreator):
# recreate new
write_data = {
"class": self.n_class,
"nodeclass": self.n_class,
"families": [self.family],
"avalon": self.data
"avalon": self.data,
"creator": self.__class__.__name__
}
if self.presets.get('fpath_template'):
@ -92,7 +95,7 @@ class CreateWriteRender(plugin.PypeCreator):
"fpath_template": ("{work}/renders/nuke/{subset}"
"/{subset}.{frame}.{ext}")})
write_node = pnlib.create_write_node(
write_node = lib.create_write_node(
self.data["subset"],
write_data,
input=selected_node)

View file

@ -25,7 +25,7 @@ class SetFrameRangeLoader(api.Loader):
def load(self, context, name, namespace, data):
from pype.hosts.nuke import lib
from pype.hosts.nuke.api import lib
version = context['version']
version_data = version.get("data", {})
@ -59,7 +59,7 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
def load(self, context, name, namespace, data):
from pype.hosts.nuke import lib
from pype.hosts.nuke.api import lib
version = context['version']
version_data = version.get("data", {})

View file

@ -1,7 +1,7 @@
from avalon import api, style, io
import nuke
import nukescripts
from pype.hosts.nuke import lib as pnlib
from pype.hosts.nuke.api import lib as pnlib
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container
reload(pnlib)

View file

@ -1,6 +1,6 @@
from avalon import api, style, io
import nuke
from pype.hosts.nuke import lib as pnlib
from pype.hosts.nuke.api import lib as pnlib
from avalon.nuke import lib as anlib
from avalon.nuke import containerise, update_container

View file

@ -4,7 +4,9 @@ import nuke
from avalon.vendor import qargparse
from avalon import api, io
from pype.hosts.nuke import presets
from pype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
class LoadImage(api.Loader):
@ -90,17 +92,10 @@ class LoadImage(api.Loader):
if colorspace:
r["colorspace"].setValue(str(colorspace))
# load nuke presets for Read's colorspace
read_clrs_presets = presets.get_colorspace_preset().get(
"nuke", {}).get("read", {})
preset_clrsp = get_imageio_input_colorspace(file)
# check if any colorspace presets for read is mathing
preset_clrsp = next((read_clrs_presets[k]
for k in read_clrs_presets
if bool(re.search(k, file))),
None)
if preset_clrsp is not None:
r["colorspace"].setValue(str(preset_clrsp))
r["colorspace"].setValue(preset_clrsp)
r["origfirst"].setValue(first)
r["first"].setValue(first)

View file

@ -2,7 +2,7 @@ from avalon import api, style, io
import nuke
import json
from collections import OrderedDict
from pype.hosts.nuke import lib
from pype.hosts.nuke.api import lib
class LoadLutsInputProcess(api.Loader):

View file

@ -1,10 +1,11 @@
import re
import nuke
import contextlib
from avalon import api, io
from pype.hosts.nuke import presets
from pype.api import get_project_settings
from pype.api import get_current_project_settings
from pype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
@contextlib.contextmanager
@ -73,12 +74,18 @@ def add_review_presets_config():
"families": list(),
"representations": list()
}
settings = get_project_settings(io.Session["AVALON_PROJECT"])
review_presets = settings["global"]["publish"].get(
"ExtractReview", {})
settings = get_current_project_settings()
review_profiles = (
settings["global"]
["publish"]
["ExtractReview"]
["profiles"]
)
outputs = {}
for profile in review_profiles:
outputs.update(profile.get("outputs", {}))
outputs = review_presets.get("outputs", {})
#
for output, properities in outputs.items():
returning["representations"].append(output)
returning["families"] += properities.get("families", [])
@ -175,17 +182,10 @@ class LoadMov(api.Loader):
if colorspace:
read_node["colorspace"].setValue(str(colorspace))
# load nuke presets for Read's colorspace
read_clrs_presets = presets.get_colorspace_preset().get(
"nuke", {}).get("read", {})
preset_clrsp = get_imageio_input_colorspace(file)
# check if any colorspace presets for read is mathing
preset_clrsp = next((read_clrs_presets[k]
for k in read_clrs_presets
if bool(re.search(k, file))),
None)
if preset_clrsp is not None:
read_node["colorspace"].setValue(str(preset_clrsp))
read_node["colorspace"].setValue(preset_clrsp)
# add additional metadata from the version to imprint Avalon knob
add_keys = [
@ -276,10 +276,11 @@ class LoadMov(api.Loader):
colorspace = version_data.get("colorspace")
if first is None:
self.log.warning("Missing start frame for updated version"
"assuming starts at frame 0 for: "
"{} ({})".format(
node['name'].value(), representation))
self.log.warning((
"Missing start frame for updated version"
"assuming starts at frame 0 for: "
"{} ({})").format(
node['name'].value(), representation))
first = 0
# fix handle start and end if none are available
@ -309,17 +310,10 @@ class LoadMov(api.Loader):
if colorspace:
node["colorspace"].setValue(str(colorspace))
# load nuke presets for Read's colorspace
read_clrs_presets = presets.get_colorspace_preset().get(
"nuke", {}).get("read", {})
preset_clrsp = get_imageio_input_colorspace(file)
# check if any colorspace presets for read is mathing
preset_clrsp = next((read_clrs_presets[k]
for k in read_clrs_presets
if bool(re.search(k, file))),
None)
if preset_clrsp is not None:
node["colorspace"].setValue(str(preset_clrsp))
node["colorspace"].setValue(preset_clrsp)
updated_dict = {}
updated_dict.update({

Some files were not shown because too many files have changed in this diff Show more