Merge pull request #2363 from pypeclub/enhancement/OP-2075_version-handling

Version handling
This commit is contained in:
Ondřej Samohel 2022-01-05 11:56:38 +01:00 committed by GitHub
commit 1964e30246
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1322 additions and 185 deletions

View file

@ -6,9 +6,18 @@ import sys
os.chdir(os.path.dirname(__file__)) # for override sys.path in Deadline
from .bootstrap_repos import BootstrapRepos
from .bootstrap_repos import (
BootstrapRepos,
OpenPypeVersion
)
from .version import __version__ as version
# Store OpenPypeVersion to 'sys.modules'
# - this makes it available in OpenPype processes without modifying
# 'sys.path' or 'PYTHONPATH'
if "OpenPypeVersion" not in sys.modules:
sys.modules["OpenPypeVersion"] = OpenPypeVersion
def open_dialog():
"""Show Igniter dialog."""
@ -22,7 +31,9 @@ def open_dialog():
if scale_attr is not None:
QtWidgets.QApplication.setAttribute(scale_attr)
app = QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
d = InstallDialog()
d.open()
@ -43,7 +54,9 @@ def open_update_window(openpype_version):
if scale_attr is not None:
QtWidgets.QApplication.setAttribute(scale_attr)
app = QtWidgets.QApplication(sys.argv)
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
d = UpdateWindow(version=openpype_version)
d.open()
@ -53,9 +66,32 @@ def open_update_window(openpype_version):
return version_path
def show_message_dialog(title, message):
"""Show dialog with a message and title to user."""
if os.getenv("OPENPYPE_HEADLESS_MODE"):
print("!!! Can't open dialog in headless mode. Exiting.")
sys.exit(1)
from Qt import QtWidgets, QtCore
from .message_dialog import MessageDialog
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)
if scale_attr is not None:
QtWidgets.QApplication.setAttribute(scale_attr)
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
dialog = MessageDialog(title, message)
dialog.open()
app.exec_()
__all__ = [
"BootstrapRepos",
"open_dialog",
"open_update_window",
"show_message_dialog",
"version"
]

View file

@ -22,7 +22,10 @@ from .user_settings import (
OpenPypeSecureRegistry,
OpenPypeSettingsRegistry
)
from .tools import get_openpype_path_from_db
from .tools import (
get_openpype_path_from_db,
get_expected_studio_version_str
)
LOG_INFO = 0
@ -60,6 +63,7 @@ class OpenPypeVersion(semver.VersionInfo):
staging = False
path = None
_VERSION_REGEX = re.compile(r"(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$") # noqa: E501
_installed_version = None
def __init__(self, *args, **kwargs):
"""Create OpenPype version.
@ -232,6 +236,390 @@ class OpenPypeVersion(semver.VersionInfo):
else:
return hash(str(self))
@staticmethod
def is_version_in_dir(
dir_item: Path, version: OpenPypeVersion) -> Tuple[bool, str]:
"""Test if path item is OpenPype version matching detected version.
If item is directory that might (based on it's name)
contain OpenPype version, check if it really does contain
OpenPype and that their versions matches.
Args:
dir_item (Path): Directory to test.
version (OpenPypeVersion): OpenPype version detected
from name.
Returns:
Tuple: State and reason, True if it is valid OpenPype version,
False otherwise.
"""
try:
# add one 'openpype' level as inside dir there should
# be many other repositories.
version_str = OpenPypeVersion.get_version_string_from_directory(
dir_item) # noqa: E501
version_check = OpenPypeVersion(version=version_str)
except ValueError:
return False, f"cannot determine version from {dir_item}"
version_main = version_check.get_main_version()
detected_main = version.get_main_version()
if version_main != detected_main:
return False, (f"dir version ({version}) and "
f"its content version ({version_check}) "
"doesn't match. Skipping.")
return True, "Versions match"
@staticmethod
def is_version_in_zip(
zip_item: Path, version: OpenPypeVersion) -> Tuple[bool, str]:
"""Test if zip path is OpenPype version matching detected version.
Open zip file, look inside and parse version from OpenPype
inside it. If there is none, or it is different from
version specified in file name, skip it.
Args:
zip_item (Path): Zip file to test.
version (OpenPypeVersion): Pype version detected
from name.
Returns:
Tuple: State and reason, True if it is valid OpenPype version,
False otherwise.
"""
# skip non-zip files
if zip_item.suffix.lower() != ".zip":
return False, "Not a zip"
try:
with ZipFile(zip_item, "r") as zip_file:
with zip_file.open(
"openpype/version.py") as version_file:
zip_version = {}
exec(version_file.read(), zip_version)
try:
version_check = OpenPypeVersion(
version=zip_version["__version__"])
except ValueError as e:
return False, str(e)
version_main = version_check.get_main_version() #
# noqa: E501
detected_main = version.get_main_version()
# noqa: E501
if version_main != detected_main:
return False, (f"zip version ({version}) "
f"and its content version "
f"({version_check}) "
"doesn't match. Skipping.")
except BadZipFile:
return False, f"{zip_item} is not a zip file"
except KeyError:
return False, "Zip does not contain OpenPype"
return True, "Versions match"
@staticmethod
def get_version_string_from_directory(repo_dir: Path) -> Union[str, None]:
"""Get version of OpenPype in given directory.
Note: in frozen OpenPype installed in user data dir, this must point
one level deeper as it is:
`openpype-version-v3.0.0/openpype/version.py`
Args:
repo_dir (Path): Path to OpenPype repo.
Returns:
str: version string.
None: if OpenPype is not found.
"""
# try to find version
version_file = Path(repo_dir) / "openpype" / "version.py"
if not version_file.exists():
return None
version = {}
with version_file.open("r") as fp:
exec(fp.read(), version)
return version['__version__']
@classmethod
def get_openpype_path(cls):
"""Path to openpype zip directory.
Path can be set through environment variable 'OPENPYPE_PATH' which
is set during start of OpenPype if is not available.
"""
return os.getenv("OPENPYPE_PATH")
@classmethod
def openpype_path_is_set(cls):
"""Path to OpenPype zip directory is set."""
if cls.get_openpype_path():
return True
return False
@classmethod
def openpype_path_is_accessible(cls):
"""Path to OpenPype zip directory is accessible.
Exists for this machine.
"""
# First check if is set
if not cls.openpype_path_is_set():
return False
# Validate existence
if Path(cls.get_openpype_path()).exists():
return True
return False
@classmethod
def get_local_versions(
cls, production: bool = None, staging: bool = None
) -> List:
"""Get all versions available on this machine.
Arguments give ability to specify if filtering is needed. If both
arguments are set to None all found versions are returned.
Args:
production (bool): Return production versions.
staging (bool): Return staging versions.
"""
# Return all local versions if arguments are set to None
if production is None and staging is None:
production = True
staging = True
elif production is None and not staging:
production = True
elif staging is None and not production:
staging = True
# Just return empty output if both are disabled
if not production and not staging:
return []
dir_to_search = Path(user_data_dir("openpype", "pypeclub"))
versions = OpenPypeVersion.get_versions_from_directory(
dir_to_search
)
filtered_versions = []
for version in versions:
if version.is_staging():
if staging:
filtered_versions.append(version)
elif production:
filtered_versions.append(version)
return list(sorted(set(filtered_versions)))
@classmethod
def get_remote_versions(
cls, production: bool = None, staging: bool = None
) -> List:
"""Get all versions available in OpenPype Path.
Arguments give ability to specify if filtering is needed. If both
arguments are set to None all found versions are returned.
Args:
production (bool): Return production versions.
staging (bool): Return staging versions.
"""
# Return all local versions if arguments are set to None
if production is None and staging is None:
production = True
staging = True
elif production is None and not staging:
production = True
elif staging is None and not production:
staging = True
# Just return empty output if both are disabled
if not production and not staging:
return []
dir_to_search = None
if cls.openpype_path_is_accessible():
dir_to_search = Path(cls.get_openpype_path())
else:
registry = OpenPypeSettingsRegistry()
try:
registry_dir = Path(str(registry.get_item("openPypePath")))
if registry_dir.exists():
dir_to_search = registry_dir
except ValueError:
# nothing found in registry, we'll use data dir
pass
if not dir_to_search:
return []
versions = cls.get_versions_from_directory(dir_to_search)
filtered_versions = []
for version in versions:
if version.is_staging():
if staging:
filtered_versions.append(version)
elif production:
filtered_versions.append(version)
return list(sorted(set(filtered_versions)))
@staticmethod
def get_versions_from_directory(openpype_dir: Path) -> List:
"""Get all detected OpenPype versions in directory.
Args:
openpype_dir (Path): Directory to scan.
Returns:
list of OpenPypeVersion
Throws:
ValueError: if invalid path is specified.
"""
if not openpype_dir.exists() and not openpype_dir.is_dir():
raise ValueError("specified directory is invalid")
_openpype_versions = []
# iterate over directory in first level and find all that might
# contain OpenPype.
for item in openpype_dir.iterdir():
# if file, strip extension, in case of dir not.
name = item.name if item.is_dir() else item.stem
result = OpenPypeVersion.version_in_str(name)
if result:
detected_version: OpenPypeVersion
detected_version = result
if item.is_dir() and not OpenPypeVersion.is_version_in_dir(
item, detected_version
)[0]:
continue
if item.is_file() and not OpenPypeVersion.is_version_in_zip(
item, detected_version
)[0]:
continue
detected_version.path = item
_openpype_versions.append(detected_version)
return sorted(_openpype_versions)
@staticmethod
def get_installed_version_str() -> str:
"""Get version of local OpenPype."""
version = {}
path = Path(os.environ["OPENPYPE_ROOT"]) / "openpype" / "version.py"
with open(path, "r") as fp:
exec(fp.read(), version)
return version["__version__"]
@classmethod
def get_installed_version(cls):
"""Get version of OpenPype inside build."""
if cls._installed_version is None:
installed_version_str = cls.get_installed_version_str()
if installed_version_str:
cls._installed_version = OpenPypeVersion(
version=installed_version_str,
path=Path(os.environ["OPENPYPE_ROOT"])
)
return cls._installed_version
@staticmethod
def get_latest_version(
staging: bool = False,
local: bool = None,
remote: bool = None
) -> OpenPypeVersion:
"""Get latest available version.
The version does not contain information about path and source.
This is utility version to get latest version from all found. Build
version is not listed if staging is enabled.
Arguments 'local' and 'remote' define if local and remote repository
versions are used. All versions are used if both are not set (or set
to 'None'). If only one of them is set to 'True' the other is disabled.
It is possible to set both to 'True' (same as both set to None) and to
'False' in that case only build version can be used.
Args:
staging (bool, optional): List staging versions if True.
local (bool, optional): List local versions if True.
remote (bool, optional): List remote versions if True.
"""
if local is None and remote is None:
local = True
remote = True
elif local is None and not remote:
local = True
elif remote is None and not local:
remote = True
installed_version = OpenPypeVersion.get_installed_version()
local_versions = []
remote_versions = []
if local:
local_versions = OpenPypeVersion.get_local_versions(
staging=staging
)
if remote:
remote_versions = OpenPypeVersion.get_remote_versions(
staging=staging
)
all_versions = local_versions + remote_versions
if not staging:
all_versions.append(installed_version)
if not all_versions:
return None
all_versions.sort()
return all_versions[-1]
@classmethod
def get_expected_studio_version(cls, staging=False, global_settings=None):
"""Expected OpenPype version that should be used at the moment.
If version is not defined in settings the latest found version is
used.
Using precached global settings is needed for usage inside OpenPype.
Args:
staging (bool): Staging version or production version.
global_settings (dict): Optional precached global settings.
Returns:
OpenPypeVersion: Version that should be used.
"""
result = get_expected_studio_version_str(staging, global_settings)
if not result:
return None
return OpenPypeVersion(version=result)
class BootstrapRepos:
"""Class for bootstrapping local OpenPype installation.
@ -301,16 +689,6 @@ class BootstrapRepos:
return v.path
return None
@staticmethod
def get_local_live_version() -> str:
"""Get version of local OpenPype."""
version = {}
path = Path(os.environ["OPENPYPE_ROOT"]) / "openpype" / "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]:
"""Get version of OpenPype in given directory.
@ -358,7 +736,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_live_version()
version = OpenPypeVersion.get_installed_version_str()
repo_dir = self.live_repo_dir
else:
version = self.get_version(repo_dir)
@ -734,6 +1112,65 @@ class BootstrapRepos:
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
@staticmethod
def find_openpype_version(version, staging):
if isinstance(version, str):
version = OpenPypeVersion(version=version)
installed_version = OpenPypeVersion.get_installed_version()
if installed_version == version:
return installed_version
local_versions = OpenPypeVersion.get_local_versions(
staging=staging, production=not staging
)
zip_version = None
for local_version in local_versions:
if local_version == version:
if local_version.path.suffix.lower() == ".zip":
zip_version = local_version
else:
return local_version
if zip_version is not None:
return zip_version
remote_versions = OpenPypeVersion.get_remote_versions(
staging=staging, production=not staging
)
for remote_version in remote_versions:
if remote_version == version:
return remote_version
return None
@staticmethod
def find_latest_openpype_version(staging):
installed_version = OpenPypeVersion.get_installed_version()
local_versions = OpenPypeVersion.get_local_versions(
staging=staging
)
remote_versions = OpenPypeVersion.get_remote_versions(
staging=staging
)
all_versions = local_versions + remote_versions
if not staging:
all_versions.append(installed_version)
if not all_versions:
return None
all_versions.sort()
latest_version = all_versions[-1]
if latest_version == installed_version:
return latest_version
if not latest_version.path.is_dir():
for version in local_versions:
if version == latest_version and version.path.is_dir():
latest_version = version
break
return latest_version
def find_openpype(
self,
openpype_path: Union[Path, str] = None,

View file

@ -12,7 +12,8 @@ from Qt.QtCore import QTimer # noqa
from .install_thread import InstallThread
from .tools import (
validate_mongo_connection,
get_openpype_path_from_db
get_openpype_path_from_db,
get_openpype_icon_path
)
from .nice_progress_bar import NiceProgressBar
@ -187,7 +188,6 @@ class InstallDialog(QtWidgets.QDialog):
current_dir = os.path.dirname(os.path.abspath(__file__))
roboto_font_path = os.path.join(current_dir, "RobotoMono-Regular.ttf")
poppins_font_path = os.path.join(current_dir, "Poppins")
icon_path = os.path.join(current_dir, "openpype_icon.png")
# Install roboto font
QtGui.QFontDatabase.addApplicationFont(roboto_font_path)
@ -196,6 +196,7 @@ class InstallDialog(QtWidgets.QDialog):
QtGui.QFontDatabase.addApplicationFont(filename)
# Load logo
icon_path = get_openpype_icon_path()
pixmap_openpype_logo = QtGui.QPixmap(icon_path)
# Set logo as icon of window
self.setWindowIcon(QtGui.QIcon(pixmap_openpype_logo))

44
igniter/message_dialog.py Normal file
View file

@ -0,0 +1,44 @@
from Qt import QtWidgets, QtGui
from .tools import (
load_stylesheet,
get_openpype_icon_path
)
class MessageDialog(QtWidgets.QDialog):
"""Simple message dialog with title, message and OK button."""
def __init__(self, title, message):
super(MessageDialog, self).__init__()
# Set logo as icon of window
icon_path = get_openpype_icon_path()
pixmap_openpype_logo = QtGui.QPixmap(icon_path)
self.setWindowIcon(QtGui.QIcon(pixmap_openpype_logo))
# Set title
self.setWindowTitle(title)
# Set message
label_widget = QtWidgets.QLabel(message, self)
ok_btn = QtWidgets.QPushButton("OK", self)
btns_layout = QtWidgets.QHBoxLayout()
btns_layout.addStretch(1)
btns_layout.addWidget(ok_btn, 0)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(label_widget, 1)
layout.addLayout(btns_layout, 0)
ok_btn.clicked.connect(self._on_ok_clicked)
self._label_widget = label_widget
self._ok_btn = ok_btn
def _on_ok_clicked(self):
self.close()
def showEvent(self, event):
super(MessageDialog, self).showEvent(event)
self.setStyleSheet(load_stylesheet())

View file

@ -16,6 +16,11 @@ from pymongo.errors import (
)
class OpenPypeVersionNotFound(Exception):
"""OpenPype version was not found in remote and local repository."""
pass
def should_add_certificate_path_to_mongo_url(mongo_url):
"""Check if should add ca certificate to mongo url.
@ -182,6 +187,28 @@ def get_openpype_path_from_db(url: str) -> Union[str, None]:
return None
def get_expected_studio_version_str(
staging=False, global_settings=None
) -> str:
"""Version that should be currently used in studio.
Args:
staging (bool): Get current version for staging.
global_settings (dict): Optional precached global settings.
Returns:
str: OpenPype version which should be used. Empty string means latest.
"""
mongo_url = os.environ.get("OPENPYPE_MONGO")
if global_settings is None:
global_settings = get_openpype_global_settings(mongo_url)
if staging:
key = "staging_version"
else:
key = "production_version"
return global_settings.get(key) or ""
def load_stylesheet() -> str:
"""Load css style sheet.
@ -192,3 +219,11 @@ def load_stylesheet() -> str:
stylesheet_path = Path(__file__).parent.resolve() / "stylesheet.css"
return stylesheet_path.read_text()
def get_openpype_icon_path() -> str:
"""Path to OpenPype icon png file."""
return os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"openpype_icon.png"
)

View file

@ -0,0 +1,85 @@
"""Lib access to OpenPypeVersion from igniter.
Access to logic from igniter is available only for OpenPype processes.
Is meant to be able check OpenPype versions for studio. The logic is dependent
on igniter's inner logic of versions.
Keep in mind that all functions except 'get_installed_version' does not return
OpenPype version located in build but versions available in remote versions
repository or locally available.
"""
import sys
def get_OpenPypeVersion():
"""Access to OpenPypeVersion class stored in sys modules."""
return sys.modules.get("OpenPypeVersion")
def op_version_control_available():
"""Check if current process has access to OpenPypeVersion."""
if get_OpenPypeVersion() is None:
return False
return True
def get_installed_version():
"""Get OpenPype version inside build.
This version is not returned by any other functions here.
"""
if op_version_control_available():
return get_OpenPypeVersion().get_installed_version()
return None
def get_available_versions(*args, **kwargs):
"""Get list of available versions."""
if op_version_control_available():
return get_OpenPypeVersion().get_available_versions(
*args, **kwargs
)
return None
def openpype_path_is_set():
"""OpenPype repository path is set in settings."""
if op_version_control_available():
return get_OpenPypeVersion().openpype_path_is_set()
return None
def openpype_path_is_accessible():
"""OpenPype version repository path can be accessed."""
if op_version_control_available():
return get_OpenPypeVersion().openpype_path_is_accessible()
return None
def get_local_versions(*args, **kwargs):
"""OpenPype versions available on this workstation."""
if op_version_control_available():
return get_OpenPypeVersion().get_local_versions(*args, **kwargs)
return None
def get_remote_versions(*args, **kwargs):
"""OpenPype versions in repository path."""
if op_version_control_available():
return get_OpenPypeVersion().get_remote_versions(*args, **kwargs)
return None
def get_latest_version(*args, **kwargs):
"""Get latest version from repository path."""
if op_version_control_available():
return get_OpenPypeVersion().get_latest_version(*args, **kwargs)
return None
def get_expected_studio_version(staging=False):
"""Expected production or staging version in studio."""
if op_version_control_available():
return get_OpenPypeVersion().get_expected_studio_version(staging)
return None

View file

@ -2,6 +2,8 @@
"studio_name": "Studio name",
"studio_code": "stu",
"admin_password": "",
"production_version": "",
"staging_version": "",
"environment": {
"__environment_keys__": {
"global": []

View file

@ -57,7 +57,7 @@ from .exceptions import (
SchemaError,
DefaultsNotDefined,
StudioDefaultsNotDefined,
BaseInvalidValueType,
BaseInvalidValue,
InvalidValueType,
InvalidKeySymbols,
SchemaMissingFileInfo,
@ -106,7 +106,7 @@ from .enum_entity import (
ToolsEnumEntity,
TaskTypeEnumEntity,
DeadlineUrlEnumEntity,
AnatomyTemplatesEnumEntity
AnatomyTemplatesEnumEntity,
)
from .list_entity import ListEntity
@ -122,12 +122,15 @@ from .dict_conditional import (
)
from .anatomy_entities import AnatomyEntity
from .op_version_entity import (
ProductionVersionsInputEntity,
StagingVersionsInputEntity
)
__all__ = (
"DefaultsNotDefined",
"StudioDefaultsNotDefined",
"BaseInvalidValueType",
"BaseInvalidValue",
"InvalidValueType",
"InvalidKeySymbols",
"SchemaMissingFileInfo",
@ -181,5 +184,8 @@ __all__ = (
"DictConditionalEntity",
"SyncServerProviders",
"AnatomyEntity"
"AnatomyEntity",
"ProductionVersionsInputEntity",
"StagingVersionsInputEntity"
)

View file

@ -9,7 +9,7 @@ from .lib import (
)
from .exceptions import (
BaseInvalidValueType,
BaseInvalidValue,
InvalidValueType,
SchemeGroupHierarchyBug,
EntitySchemaError
@ -437,7 +437,7 @@ class BaseItemEntity(BaseEntity):
try:
new_value = self.convert_to_valid_type(value)
except BaseInvalidValueType:
except BaseInvalidValue:
new_value = NOT_SET
if new_value is not NOT_SET:

View file

@ -1,7 +1,7 @@
from .lib import STRING_TYPE
from .input_entities import InputEntity
from .exceptions import (
BaseInvalidValueType,
BaseInvalidValue,
InvalidValueType
)
@ -47,7 +47,7 @@ class ColorEntity(InputEntity):
reason = "Color entity expect 4 items in list got {}".format(
len(value)
)
raise BaseInvalidValueType(reason, self.path)
raise BaseInvalidValue(reason, self.path)
new_value = []
for item in value:
@ -60,7 +60,7 @@ class ColorEntity(InputEntity):
reason = (
"Color entity expect 4 integers in range 0-255 got {}"
).format(value)
raise BaseInvalidValueType(reason, self.path)
raise BaseInvalidValue(reason, self.path)
new_value.append(item)
# Make sure

View file

@ -15,14 +15,14 @@ class StudioDefaultsNotDefined(Exception):
super(StudioDefaultsNotDefined, self).__init__(msg)
class BaseInvalidValueType(Exception):
class BaseInvalidValue(Exception):
def __init__(self, reason, path):
msg = "Path \"{}\". {}".format(path, reason)
self.msg = msg
super(BaseInvalidValueType, self).__init__(msg)
super(BaseInvalidValue, self).__init__(msg)
class InvalidValueType(BaseInvalidValueType):
class InvalidValueType(BaseInvalidValue):
def __init__(self, valid_types, invalid_type, path):
joined_types = ", ".join(
[str(valid_type) for valid_type in valid_types]

View file

@ -441,6 +441,16 @@ class TextEntity(InputEntity):
# GUI attributes
self.multiline = self.schema_data.get("multiline", False)
self.placeholder_text = self.schema_data.get("placeholder")
self.value_hints = self.schema_data.get("value_hints") or []
def schema_validations(self):
if self.multiline and self.value_hints:
reason = (
"TextEntity entity can't use value hints"
" for multiline input (yet)."
)
raise EntitySchemaError(self, reason)
super(TextEntity, self).schema_validations()
def _convert_to_valid_type(self, value):
# Allow numbers converted to string

View file

@ -0,0 +1,85 @@
from openpype.lib.openpype_version import (
get_remote_versions,
get_OpenPypeVersion,
get_installed_version
)
from .input_entities import TextEntity
from .lib import (
OverrideState,
NOT_SET
)
from .exceptions import BaseInvalidValue
class OpenPypeVersionInput(TextEntity):
"""Entity to store OpenPype version to use.
Settings created on another machine may affect available versions
on current user's machine. Text input element is provided to explicitly
set version not yet showing up the user's machine.
It is possible to enter empty string. In that case is used any latest
version. Any other string must match regex of OpenPype version semantic.
"""
def _item_initialization(self):
super(OpenPypeVersionInput, self)._item_initialization()
self.multiline = False
self.placeholder_text = "Latest"
self.value_hints = []
def _get_openpype_versions(self):
"""This is abstract method returning version hints for UI purposes."""
raise NotImplementedError((
"{} does not have implemented '_get_openpype_versions'"
).format(self.__class__.__name__))
def set_override_state(self, state, *args, **kwargs):
"""Update value hints for UI purposes."""
value_hints = []
if state is OverrideState.STUDIO:
versions = self._get_openpype_versions()
for version in versions:
version_str = str(version)
if version_str not in value_hints:
value_hints.append(version_str)
self.value_hints = value_hints
super(OpenPypeVersionInput, self).set_override_state(
state, *args, **kwargs
)
def convert_to_valid_type(self, value):
"""Add validation of version regex."""
if value and value is not NOT_SET:
OpenPypeVersion = get_OpenPypeVersion()
if OpenPypeVersion is not None:
try:
OpenPypeVersion(version=value)
except Exception:
raise BaseInvalidValue(
"Value \"{}\"is not valid version format.".format(
value
),
self.path
)
return super(OpenPypeVersionInput, self).convert_to_valid_type(value)
class ProductionVersionsInputEntity(OpenPypeVersionInput):
"""Entity meant only for global settings to define production version."""
schema_types = ["production-versions-text"]
def _get_openpype_versions(self):
versions = get_remote_versions(staging=False, production=True)
versions.append(get_installed_version())
return sorted(versions)
class StagingVersionsInputEntity(OpenPypeVersionInput):
"""Entity meant only for global settings to define staging version."""
schema_types = ["staging-versions-text"]
def _get_openpype_versions(self):
versions = get_remote_versions(staging=True, production=False)
return sorted(versions)

View file

@ -20,7 +20,7 @@
},
{
"type": "label",
"label": "This is <b>NOT a securely stored password!</b>. It only acts as a simple barrier to stop users from accessing studio wide settings."
"label": "This is <b>NOT a securely stored password!</b> It only acts as a simple barrier to stop users from accessing studio wide settings."
},
{
"type": "text",
@ -30,6 +30,23 @@
{
"type": "splitter"
},
{
"type": "label",
"label": "Define explicit OpenPype version that should be used. Keep empty to use latest available version."
},
{
"type": "production-versions-text",
"key": "production_version",
"label": "Production version"
},
{
"type": "staging-versions-text",
"key": "staging_version",
"label": "Staging version"
},
{
"type": "splitter"
},
{
"key": "environment",
"label": "Environment",

View file

@ -168,7 +168,13 @@ class CacheValues:
class MongoSettingsHandler(SettingsHandler):
"""Settings handler that use mongo for storing and loading of settings."""
global_general_keys = ("openpype_path", "admin_password", "disk_mapping")
global_general_keys = (
"openpype_path",
"admin_password",
"disk_mapping",
"production_version",
"staging_version"
)
def __init__(self):
# Get mongo connection

View file

@ -111,7 +111,9 @@
"focus-border": "#839caf",
"image-btn": "#bfccd6",
"image-btn-hover": "#189aea",
"image-btn-disabled": "#bfccd6"
"image-btn-disabled": "#bfccd6",
"version-exists": "#458056",
"version-not-found": "#ffc671"
}
}
}

View file

@ -536,6 +536,27 @@ QAbstractItemView::branch:!has-children:!has-siblings:adjoins-item {
background: transparent;
}
CompleterView {
border: 1px solid #555555;
background: {color:bg-inputs};
}
CompleterView::item:selected {
background: {color:bg-view-hover};
}
CompleterView::item:selected:hover {
background: {color:bg-view-hover};
}
CompleterView::right-arrow {
min-width: 10px;
}
CompleterView::separator {
background: {color:bg-menu-separator};
height: 2px;
margin-right: 5px;
}
/* Progress bar */
QProgressBar {
@ -1158,6 +1179,14 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
border-radius: 5px;
}
#OpenPypeVersionLabel[state="success"] {
color: {color:settings:version-exists};
}
#OpenPypeVersionLabel[state="warning"] {
color: {color:settings:version-not-found};
}
#ShadowWidget {
font-size: 36pt;
}

View file

@ -32,6 +32,15 @@ class BaseWidget(QtWidgets.QWidget):
self.label_widget = None
self.create_ui()
@staticmethod
def set_style_property(obj, property_name, property_value):
"""Change QWidget property and polish it's style."""
if obj.property(property_name) == property_value:
return
obj.setProperty(property_name, property_value)
obj.style().polish(obj)
def scroll_to(self, widget):
self.category_widget.scroll_to(widget)

View file

@ -29,6 +29,9 @@ from openpype.settings.entities import (
StudioDefaultsNotDefined,
SchemaError
)
from openpype.settings.entities.op_version_entity import (
OpenPypeVersionInput
)
from openpype.settings import SaveWarningExc
from .widgets import ProjectListWidget
@ -47,6 +50,7 @@ from .item_widgets import (
BoolWidget,
DictImmutableKeysWidget,
TextWidget,
OpenPypeVersionText,
NumberWidget,
RawJsonWidget,
EnumeratorWidget,
@ -118,6 +122,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
elif isinstance(entity, BoolEntity):
return BoolWidget(*args)
elif isinstance(entity, OpenPypeVersionInput):
return OpenPypeVersionText(*args)
elif isinstance(entity, TextEntity):
return TextWidget(*args)

View file

@ -2,6 +2,10 @@ import json
from Qt import QtWidgets, QtCore, QtGui
from openpype.widgets.sliders import NiceSlider
from openpype.tools.settings import CHILD_OFFSET
from openpype.settings.entities.exceptions import BaseInvalidValue
from .widgets import (
ExpandingWidget,
NumberSpinBox,
@ -22,9 +26,6 @@ from .base import (
InputWidget
)
from openpype.widgets.sliders import NiceSlider
from openpype.tools.settings import CHILD_OFFSET
class DictImmutableKeysWidget(BaseWidget):
def create_ui(self):
@ -378,6 +379,16 @@ class TextWidget(InputWidget):
self.input_field.focused_in.connect(self._on_input_focus)
self.input_field.textChanged.connect(self._on_value_change)
self._refresh_completer()
def _refresh_completer(self):
# Multiline entity can't have completer
# - there is not space for this UI component
if self.entity.multiline:
return
self.input_field.update_completer_values(self.entity.value_hints)
def _on_input_focus(self):
self.focused_in()
@ -406,6 +417,86 @@ class TextWidget(InputWidget):
self.entity.set(self.input_value())
class OpenPypeVersionText(TextWidget):
def __init__(self, *args, **kwargs):
self._info_widget = None
super(OpenPypeVersionText, self).__init__(*args, **kwargs)
def create_ui(self):
super(OpenPypeVersionText, self).create_ui()
info_widget = QtWidgets.QLabel(self)
info_widget.setObjectName("OpenPypeVersionLabel")
self.content_layout.addWidget(info_widget, 1)
self._info_widget = info_widget
def _update_info_widget(self):
value = self.input_value()
message = ""
tooltip = ""
state = None
if self._is_invalid:
message = "Invalid OpenPype version format"
elif value == "":
message = "Use latest available version"
tooltip = (
"Latest version from OpenPype zip repository will be used"
)
elif value in self.entity.value_hints:
state = "success"
message = "Version {} will be used".format(value)
else:
state = "warning"
message = (
"Version {} not found in listed versions".format(value)
)
if self.entity.value_hints:
tooltip = "Listed versions: {}".format(", ".join(
['"{}"'.format(hint) for hint in self.entity.value_hints]
))
else:
tooltip = "No versions were listed"
self._info_widget.setText(message)
self._info_widget.setToolTip(tooltip)
self.set_style_property(self._info_widget, "state", state)
def set_entity_value(self):
super(OpenPypeVersionText, self).set_entity_value()
self._invalidate()
self._update_info_widget()
def _on_value_change_timer(self):
value = self.input_value()
self._invalidate()
if not self.is_invalid:
self.entity.set(value)
self.update_style()
else:
# Manually trigger hierachical style update
self.ignore_input_changes.set_ignore(True)
self.ignore_input_changes.set_ignore(False)
self._update_info_widget()
def _invalidate(self):
value = self.input_value()
try:
self.entity.convert_to_valid_type(value)
is_invalid = False
except BaseInvalidValue:
is_invalid = True
self._is_invalid = is_invalid
def _on_entity_change(self):
super(OpenPypeVersionText, self)._on_entity_change()
self._refresh_completer()
class NumberWidget(InputWidget):
_slider_widget = None

View file

@ -1,4 +1,5 @@
import os
import copy
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome
from avalon.mongodb import (
@ -25,13 +26,235 @@ from .constants import (
)
class CompleterFilter(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super(CompleterFilter, self).__init__(*args, **kwargs)
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self._text_filter = ""
def set_text_filter(self, text):
if self._text_filter == text:
return
self._text_filter = text
self.invalidateFilter()
def filterAcceptsRow(self, row, parent_index):
if not self._text_filter:
return True
model = self.sourceModel()
index = model.index(row, self.filterKeyColumn(), parent_index)
value = index.data(QtCore.Qt.DisplayRole)
if self._text_filter in value:
if self._text_filter == value:
return False
return True
return False
class CompleterView(QtWidgets.QListView):
row_activated = QtCore.Signal(str)
def __init__(self, parent):
super(CompleterView, self).__init__(parent)
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint
| QtCore.Qt.Tool
)
delegate = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(delegate)
model = QtGui.QStandardItemModel()
filter_model = CompleterFilter()
filter_model.setSourceModel(model)
self.setModel(filter_model)
# self.installEventFilter(parent)
self.clicked.connect(self._on_activated)
self._last_loaded_values = None
self._model = model
self._filter_model = filter_model
self._delegate = delegate
def _on_activated(self, index):
if index.isValid():
value = index.data(QtCore.Qt.DisplayRole)
self.row_activated.emit(value)
def set_text_filter(self, text):
self._filter_model.set_text_filter(text)
self._update_geo()
def sizeHint(self):
result = super(CompleterView, self).sizeHint()
if self._filter_model.rowCount() == 0:
result.setHeight(0)
return result
def showEvent(self, event):
super(CompleterView, self).showEvent(event)
self._update_geo()
def _update_geo(self):
size_hint = self.sizeHint()
self.resize(size_hint.width(), size_hint.height())
def update_values(self, values):
if not values:
values = []
if self._last_loaded_values == values:
return
self._last_loaded_values = copy.deepcopy(values)
root_item = self._model.invisibleRootItem()
existing_values = {}
for row in reversed(range(root_item.rowCount())):
child = root_item.child(row)
value = child.data(QtCore.Qt.DisplayRole)
if value not in values:
root_item.removeRows(child.row())
else:
existing_values[value] = child
for row, value in enumerate(values):
if value in existing_values:
item = existing_values[value]
if item.row() == row:
continue
else:
item = QtGui.QStandardItem(value)
item.setEditable(False)
root_item.setChild(row, item)
self._update_geo()
def _get_selected_row(self):
indexes = self.selectionModel().selectedIndexes()
if not indexes:
return -1
return indexes[0].row()
def _select_row(self, row):
index = self._filter_model.index(row, 0)
self.setCurrentIndex(index)
def move_up(self):
rows = self._filter_model.rowCount()
if rows == 0:
return
selected_row = self._get_selected_row()
if selected_row < 0:
new_row = rows - 1
else:
new_row = selected_row - 1
if new_row < 0:
new_row = rows - 1
if new_row != selected_row:
self._select_row(new_row)
def move_down(self):
rows = self._filter_model.rowCount()
if rows == 0:
return
selected_row = self._get_selected_row()
if selected_row < 0:
new_row = 0
else:
new_row = selected_row + 1
if new_row >= rows:
new_row = 0
if new_row != selected_row:
self._select_row(new_row)
def enter_pressed(self):
selected_row = self._get_selected_row()
if selected_row < 0:
return
index = self._filter_model.index(selected_row, 0)
self._on_activated(index)
class SettingsLineEdit(PlaceholderLineEdit):
focused_in = QtCore.Signal()
def __init__(self, *args, **kwargs):
super(SettingsLineEdit, self).__init__(*args, **kwargs)
self._completer = None
self.textChanged.connect(self._on_text_change)
def _on_text_change(self, text):
if self._completer is not None:
self._completer.set_text_filter(text)
def _update_completer(self):
if self._completer is None or not self._completer.isVisible():
return
point = self.frameGeometry().bottomLeft()
new_point = self.mapToGlobal(point)
self._completer.move(new_point)
def focusInEvent(self, event):
super(SettingsLineEdit, self).focusInEvent(event)
self.focused_in.emit()
if self._completer is None:
return
self._completer.show()
self._update_completer()
def focusOutEvent(self, event):
super(SettingsLineEdit, self).focusOutEvent(event)
if self._completer is not None:
self._completer.hide()
def paintEvent(self, event):
super(SettingsLineEdit, self).paintEvent(event)
self._update_completer()
def update_completer_values(self, values):
if not values and self._completer is None:
return
self._create_completer()
self._completer.update_values(values)
def _create_completer(self):
if self._completer is None:
self._completer = CompleterView(self)
self._completer.row_activated.connect(self._completer_activated)
def _completer_activated(self, text):
self.setText(text)
def keyPressEvent(self, event):
if self._completer is None:
super(SettingsLineEdit, self).keyPressEvent(event)
return
key = event.key()
if key == QtCore.Qt.Key_Up:
self._completer.move_up()
elif key == QtCore.Qt.Key_Down:
self._completer.move_down()
elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self._completer.enter_pressed()
else:
super(SettingsLineEdit, self).keyPressEvent(event)
class SettingsPlainTextEdit(QtWidgets.QPlainTextEdit):
focused_in = QtCore.Signal()

303
start.py
View file

@ -196,7 +196,8 @@ from igniter import BootstrapRepos # noqa: E402
from igniter.tools import (
get_openpype_global_settings,
get_openpype_path_from_db,
validate_mongo_connection
validate_mongo_connection,
OpenPypeVersionNotFound
) # noqa
from igniter.bootstrap_repos import OpenPypeVersion # noqa: E402
@ -467,23 +468,41 @@ def _process_arguments() -> tuple:
use_version = None
use_staging = False
commands = []
for arg in sys.argv:
if arg == "--use-version":
_print("!!! Please use option --use-version like:")
_print(" --use-version=3.0.0")
sys.exit(1)
if arg.startswith("--use-version="):
m = re.search(
r"--use-version=(?P<version>\d+\.\d+\.\d+(?:\S*)?)", arg)
if m and m.group('version'):
use_version = m.group('version')
_print(">>> Requested version [ {} ]".format(use_version))
sys.argv.remove(arg)
if "+staging" in use_version:
use_staging = True
break
# OpenPype version specification through arguments
use_version_arg = "--use-version"
for arg in sys.argv:
if arg.startswith(use_version_arg):
# Remove arg from sys argv
sys.argv.remove(arg)
# Extract string after use version arg
use_version_value = arg[len(use_version_arg):]
if (
not use_version_value
or not use_version_value.startswith("=")
):
_print("!!! Please use option --use-version like:")
_print(" --use-version=3.0.0")
sys.exit(1)
version_str = use_version_value[1:]
use_version = None
if version_str.lower() == "latest":
use_version = "latest"
else:
m = re.search(
r"(?P<version>\d+\.\d+\.\d+(?:\S*)?)", version_str
)
if m and m.group('version'):
use_version = m.group('version')
_print(">>> Requested version [ {} ]".format(use_version))
if "+staging" in use_version:
use_staging = True
break
if use_version is None:
_print("!!! Requested version isn't in correct format.")
_print((" Use --list-versions to find out"
" proper version string."))
@ -521,7 +540,7 @@ def _process_arguments() -> tuple:
if os.getenv("OPENPYPE_HEADLESS_MODE") == "1":
_print("!!! Cannot open Igniter dialog in headless mode.")
sys.exit(1)
import igniter
return_code = igniter.open_dialog()
# this is when we want to run OpenPype without installing anything.
@ -646,67 +665,62 @@ def _find_frozen_openpype(use_version: str = None,
(if requested).
"""
version_path = None
openpype_version = None
openpype_versions = bootstrap.find_openpype(include_zips=True,
staging=use_staging)
# Collect OpenPype versions
installed_version = OpenPypeVersion.get_installed_version()
# Expected version that should be used by studio settings
# - this option is used only if version is not explictly set and if
# studio has set explicit version in settings
studio_version = OpenPypeVersion.get_expected_studio_version(use_staging)
if use_version is not None:
# Specific version is defined
if use_version.lower() == "latest":
# Version says to use latest version
_print("Finding latest version defined by use version")
openpype_version = bootstrap.find_latest_openpype_version(
use_staging
)
else:
_print("Finding specified version \"{}\"".format(use_version))
openpype_version = bootstrap.find_openpype_version(
use_version, use_staging
)
if openpype_version is None:
raise OpenPypeVersionNotFound(
"Requested version \"{}\" was not found.".format(
use_version
)
)
elif studio_version is not None:
# Studio has defined a version to use
_print("Finding studio version \"{}\"".format(studio_version))
openpype_version = bootstrap.find_openpype_version(
studio_version, use_staging
)
if openpype_version is None:
raise OpenPypeVersionNotFound((
"Requested OpenPype version \"{}\" defined by settings"
" was not found."
).format(studio_version))
else:
# Default behavior to use latest version
_print("Finding latest version")
openpype_version = bootstrap.find_latest_openpype_version(
use_staging
)
if openpype_version is None:
if use_staging:
reason = "Didn't find any staging versions."
else:
reason = "Didn't find any versions."
raise OpenPypeVersionNotFound(reason)
# get local frozen version and add it to detected version so if it is
# newer it will be used instead.
local_version_str = bootstrap.get_version(
Path(os.environ["OPENPYPE_ROOT"]))
if local_version_str:
local_version = OpenPypeVersion(
version=local_version_str,
path=Path(os.environ["OPENPYPE_ROOT"]))
if local_version not in openpype_versions:
openpype_versions.append(local_version)
openpype_versions.sort()
# if latest is currently running, ditch whole list
# and run from current without installing it.
if local_version == openpype_versions[-1]:
os.environ["OPENPYPE_TRYOUT"] = "1"
openpype_versions = []
else:
_print("!!! Warning: cannot determine current running version.")
if not os.getenv("OPENPYPE_TRYOUT"):
try:
# use latest one found (last in the list is latest)
openpype_version = openpype_versions[-1]
except IndexError:
# no OpenPype version found, run Igniter and ask for them.
_print('*** No OpenPype versions found.')
if os.getenv("OPENPYPE_HEADLESS_MODE") == "1":
_print("!!! Cannot open Igniter dialog in headless mode.")
sys.exit(1)
_print("--- launching setup UI ...")
import igniter
return_code = igniter.open_dialog()
if return_code == 2:
os.environ["OPENPYPE_TRYOUT"] = "1"
if return_code == 3:
# run OpenPype after installation
_print('>>> Finding OpenPype again ...')
openpype_versions = bootstrap.find_openpype(
staging=use_staging)
try:
openpype_version = openpype_versions[-1]
except IndexError:
_print(("!!! Something is wrong and we didn't "
"found it again."))
sys.exit(1)
elif return_code != 2:
_print(f" . finished ({return_code})")
sys.exit(return_code)
if not openpype_versions:
# no openpype versions found anyway, lets use then the one
# shipped with frozen OpenPype
if not os.getenv("OPENPYPE_TRYOUT"):
_print("*** Still no luck finding OpenPype.")
_print(("*** We'll try to use the one coming "
"with OpenPype installation."))
if installed_version == openpype_version:
version_path = _bootstrap_from_code(use_version, use_staging)
openpype_version = OpenPypeVersion(
version=BootstrapRepos.get_version(version_path),
@ -714,22 +728,6 @@ def _find_frozen_openpype(use_version: str = None,
_initialize_environment(openpype_version)
return version_path
# get path of version specified in `--use-version`
local_version = bootstrap.get_version(OPENPYPE_ROOT)
if use_version and use_version != local_version:
# force the one user has selected
openpype_version = None
openpype_versions = bootstrap.find_openpype(include_zips=True,
staging=use_staging)
v: OpenPypeVersion
found = [v for v in openpype_versions if str(v) == use_version]
if found:
openpype_version = sorted(found)[-1]
if not openpype_version:
_print(f"!!! Requested version {use_version} was not found.")
list_versions(openpype_versions, local_version)
sys.exit(1)
# test if latest detected is installed (in user data dir)
is_inside = False
try:
@ -742,12 +740,12 @@ def _find_frozen_openpype(use_version: str = None,
if not is_inside:
# install latest version to user data dir
if os.getenv("OPENPYPE_HEADLESS_MODE", "0") != "1":
import igniter
version_path = igniter.open_update_window(openpype_version)
else:
if os.getenv("OPENPYPE_HEADLESS_MODE") == "1":
version_path = bootstrap.install_version(
openpype_version, force=True)
openpype_version, force=True
)
else:
version_path = igniter.open_update_window(openpype_version)
openpype_version.path = version_path
_initialize_environment(openpype_version)
@ -783,6 +781,13 @@ def _bootstrap_from_code(use_version, use_staging):
# run through repos and add them to `sys.path` and `PYTHONPATH`
# set root
_openpype_root = OPENPYPE_ROOT
# Unset use version if latest should be used
# - when executed from code then code is expected as latest
# - when executed from build then build is already marked as latest
# in '_find_frozen_openpype'
if use_version and use_version.lower() == "latest":
use_version = None
if getattr(sys, 'frozen', False):
local_version = bootstrap.get_version(Path(_openpype_root))
switch_str = f" - will switch to {use_version}" if use_version else ""
@ -790,47 +795,45 @@ def _bootstrap_from_code(use_version, use_staging):
assert local_version
else:
# get current version of OpenPype
local_version = bootstrap.get_local_live_version()
local_version = OpenPypeVersion.get_installed_version_str()
version_to_use = None
openpype_versions = bootstrap.find_openpype(
include_zips=True, staging=use_staging)
if use_staging and not use_version:
try:
version_to_use = openpype_versions[-1]
except IndexError:
_print("!!! No staging versions are found.")
list_versions(openpype_versions, local_version)
sys.exit(1)
# All cases when should be used different version than build
if (use_version and use_version != local_version) or use_staging:
if use_version:
# Explicit version should be used
version_to_use = bootstrap.find_openpype_version(
use_version, use_staging
)
if version_to_use is None:
raise OpenPypeVersionNotFound(
"Requested version \"{}\" was not found.".format(
use_version
)
)
else:
# Staging version should be used
version_to_use = bootstrap.find_latest_openpype_version(
use_staging
)
if version_to_use is None:
if use_staging:
reason = "Didn't find any staging versions."
else:
# This reason is backup for possible bug in code
reason = "Didn't find any versions."
raise OpenPypeVersionNotFound(reason)
# Start extraction of version if needed
if version_to_use.path.is_file():
version_to_use.path = bootstrap.extract_openpype(
version_to_use)
version_to_use.path = bootstrap.extract_openpype(version_to_use)
bootstrap.add_paths_from_directory(version_to_use.path)
os.environ["OPENPYPE_VERSION"] = str(version_to_use)
os.environ["OPENPYPE_VERSION"] = use_version
version_path = version_to_use.path
os.environ["OPENPYPE_REPOS_ROOT"] = (version_path / "openpype").as_posix() # noqa: E501
os.environ["OPENPYPE_REPOS_ROOT"] = (
version_path / "openpype"
).as_posix()
_openpype_root = version_to_use.path.as_posix()
elif use_version and use_version != local_version:
v: OpenPypeVersion
found = [v for v in openpype_versions if str(v) == use_version]
if found:
version_to_use = sorted(found)[-1]
if version_to_use:
# use specified
if version_to_use.path.is_file():
version_to_use.path = bootstrap.extract_openpype(
version_to_use)
bootstrap.add_paths_from_directory(version_to_use.path)
os.environ["OPENPYPE_VERSION"] = use_version
version_path = version_to_use.path
os.environ["OPENPYPE_REPOS_ROOT"] = (version_path / "openpype").as_posix() # noqa: E501
_openpype_root = version_to_use.path.as_posix()
else:
_print(f"!!! Requested version {use_version} was not found.")
list_versions(openpype_versions, local_version)
sys.exit(1)
else:
os.environ["OPENPYPE_VERSION"] = local_version
version_path = Path(_openpype_root)
@ -889,16 +892,6 @@ def boot():
# ------------------------------------------------------------------------
_startup_validations()
# ------------------------------------------------------------------------
# Play animation
# ------------------------------------------------------------------------
# from igniter.terminal_splash import play_animation
# don't play for silenced commands
# if all(item not in sys.argv for item in silent_commands):
# play_animation()
# ------------------------------------------------------------------------
# Process arguments
# ------------------------------------------------------------------------
@ -940,7 +933,7 @@ def boot():
if getattr(sys, 'frozen', False):
local_version = bootstrap.get_version(Path(OPENPYPE_ROOT))
else:
local_version = bootstrap.get_local_live_version()
local_version = OpenPypeVersion.get_installed_version_str()
if "validate" in commands:
_print(f">>> Validating version [ {use_version} ]")
@ -969,7 +962,6 @@ def boot():
)
sys.exit(1)
if not openpype_path:
_print("*** Cannot get OpenPype path from database.")
@ -989,7 +981,7 @@ def boot():
if getattr(sys, 'frozen', False):
local_version = bootstrap.get_version(Path(_openpype_root))
else:
local_version = bootstrap.get_local_live_version()
local_version = OpenPypeVersion.get_installed_version_str()
list_versions(openpype_versions, local_version)
sys.exit(1)
@ -1003,6 +995,15 @@ def boot():
# find versions of OpenPype to be used with frozen code
try:
version_path = _find_frozen_openpype(use_version, use_staging)
except OpenPypeVersionNotFound as exc:
message = str(exc)
_print(message)
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
list_versions(openpype_versions, local_version)
else:
igniter.show_message_dialog("Version not found", message)
sys.exit(1)
except RuntimeError as e:
# no version to run
_print(f"!!! {e}")
@ -1015,7 +1016,17 @@ def boot():
sys.exit(1)
_print(f"--- version is valid")
else:
version_path = _bootstrap_from_code(use_version, use_staging)
try:
version_path = _bootstrap_from_code(use_version, use_staging)
except OpenPypeVersionNotFound as exc:
message = str(exc)
_print(message)
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
list_versions(openpype_versions, local_version)
else:
igniter.show_message_dialog("Version not found", message)
sys.exit(1)
# set this to point either to `python` from venv in case of live code
# or to `openpype` or `openpype_console` in case of frozen code

View file

@ -140,9 +140,10 @@ def test_search_string_for_openpype_version(printer):
]
for ver_string in strings:
printer(f"testing {ver_string[0]} should be {ver_string[1]}")
assert OpenPypeVersion.version_in_str(ver_string[0]) == \
ver_string[1]
assert isinstance(
OpenPypeVersion.version_in_str(ver_string[0]),
OpenPypeVersion if ver_string[1] else type(None)
)
@pytest.mark.slow
def test_install_live_repos(fix_bootstrap, printer, monkeypatch, pytestconfig):