change version comparsion, handling of venv and startup

This commit is contained in:
Ondrej Samohel 2021-01-15 22:42:16 +01:00
parent 2d3bb6a4d9
commit 10686e06c5
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
13 changed files with 1295 additions and 114 deletions

View file

@ -14,8 +14,7 @@ from zipfile import ZipFile, BadZipFile
from appdirs import user_data_dir
from speedcopy import copyfile
from pype.lib import PypeSettingsRegistry
from pype.version import __version__
from .user_settings import PypeSettingsRegistry
from .tools import load_environments
@ -24,23 +23,23 @@ 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<variant>staging)|(?P<client>.+))(-(?P<cli>.+))?)?") # noqa: E501
r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<sub>\d+)(-(?P<var1>staging)|-(?P<client>.+)(-(?P<var2>staging)))?") # noqa: E501
@property
def version(self):
@ -58,12 +57,12 @@ 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
if (
major is None or minor is None or subversion is None
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:
@ -85,12 +84,13 @@ 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
@classmethod
@ -101,10 +101,10 @@ class PypeVersion:
"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)
@ -125,26 +125,48 @@ 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
# Directory takes precedence over file
if (
self.path
and other.path
and other.path.is_dir()
and self.path.is_file()
):
# 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 self.major == other.major and self.minor == other.minor and \
self.subversion == other.subversion and self.variant == "staging"
# 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."""
@ -252,7 +274,12 @@ class BootstrapRepos:
@staticmethod
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]:
@ -494,6 +521,7 @@ class BootstrapRepos:
def find_pype(
self,
pype_path: Path = None,
staging: bool = False,
include_zips: bool = False) -> Union[List[PypeVersion], None]:
"""Get ordered dict of detected Pype version.
@ -505,6 +533,8 @@ 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.
@ -549,6 +579,7 @@ class BootstrapRepos:
result = PypeVersion.version_in_str(name)
if result[0]:
detected_version: PypeVersion
detected_version = result[1]
if file.is_dir():
@ -614,7 +645,11 @@ class BootstrapRepos:
continue
detected_version.path = file
_pype_versions.append(detected_version)
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)

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,13 +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 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):
@ -21,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):
@ -109,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)

View file

@ -27,11 +27,12 @@ def play_animation():
animation_length = int(len(animation) / frame_size)
current_frame = 0
for _ in range(animation_length):
frame = ""
y = 0
for scanline in animation[current_frame:current_frame + frame_size]:
frame += scanline
y += 1
frame = "".join(
scanline
for y, scanline in enumerate(
animation[current_frame : current_frame + frame_size]
)
)
with term.location(0, 0):
# term.aquamarine3_bold(frame)

View file

@ -28,8 +28,6 @@ import platform
import appdirs
import six
from ..version import __version__
@six.add_metaclass(ABCMeta)
class ASettingRegistry():
@ -213,11 +211,12 @@ class IniSettingRegistry(ASettingRegistry):
# 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)
print("# Generated by Pype {}".format(version), cfg)
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
print("# {}".format(now), cfg)
@ -368,7 +367,7 @@ class JSONSettingRegistry(ASettingRegistry):
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
header = {
"__metadata__": {
"pype-version": __version__,
"pype-version": os.getenv("PYPE_VERSION", "N/A"),
"generated": now
},
"registry": {}
@ -459,9 +458,9 @@ class PypeSettingsRegistry(JSONSettingRegistry):
product (str): Additional name used for path construction.
"""
vendor = "pypeclub"
product = "pype"
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)

View file

@ -66,7 +66,8 @@ build_options = dict(
includes=includes,
excludes=excludes,
bin_includes=bin_includes,
include_files=include_files
include_files=include_files,
optimize=0
)
icon_path = pype_root / "igniter" / "pype.ico"

View file

@ -99,7 +99,16 @@ import traceback
import subprocess
from pathlib import Path
import acre
# add dependencies folder to sys.pat for frozen code
if getattr(sys, 'frozen', False):
frozen_libs = os.path.normpath(
os.path.join(os.path.dirname(sys.executable), "dependencies"))
sys.path.append(frozen_libs)
# add stuff from `<frozen>/dependencies` to PYTHONPATH.
pythonpath = os.getenv("PYTHONPATH", "")
paths = pythonpath.split(os.pathsep)
paths.append(frozen_libs)
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
from igniter import BootstrapRepos
from igniter.tools import load_environments
@ -116,6 +125,15 @@ def set_environments() -> None:
better handling of environments
"""
try:
import acre
except ImportError:
if getattr(sys, 'frozen', False):
sys.path.append(os.path.join(
os.path.dirname(sys.executable),
"dependencies"
))
import acre
try:
env = load_environments(["global"])
except OSError as e:
@ -163,6 +181,7 @@ def set_modules_environments():
"""
from pype.modules import ModulesManager
import acre
modules_manager = ModulesManager()
@ -267,7 +286,8 @@ def _find_frozen_pype(use_version: str = None,
"""
pype_version = None
pype_versions = bootstrap.find_pype(include_zips=True)
pype_versions = bootstrap.find_pype(include_zips=True,
staging=use_staging)
try:
# use latest one found (last in the list is latest)
pype_version = pype_versions[-1]
@ -281,16 +301,6 @@ def _find_frozen_pype(use_version: str = None,
if not pype_versions:
raise RuntimeError("No Pype versions found.")
# find only staging versions
if use_staging:
staging_versions = [v for v in pype_versions if v.is_staging()]
if not staging_versions:
raise RuntimeError("No Pype staging versions found.")
staging_versions.sort()
# get latest staging version (last in the list is latest)
pype_version = staging_versions[-1]
# get path of version specified in `--use-version`
version_path = BootstrapRepos.get_version_path_from_list(
use_version, pype_versions)
@ -322,18 +332,11 @@ def _find_frozen_pype(use_version: str = None,
print(">>> Extracting zip file ...")
version_path = bootstrap.extract_pype(pype_version)
os.environ["PYPE_VERSION"] = pype_version.version
# inject version to Python environment (sys.path, ...)
print(">>> Injecting Pype version to running environment ...")
bootstrap.add_paths_from_directory(version_path)
# add stuff from `<frozen>/lib` to PYTHONPATH.
pythonpath = os.getenv("PYTHONPATH", "")
paths = pythonpath.split(os.pathsep)
frozen_libs = os.path.normpath(
os.path.join(os.path.dirname(sys.executable), "lib"))
paths.append(frozen_libs)
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
# set PYPE_ROOT to point to currently used Pype version.
os.environ["PYPE_ROOT"] = os.path.normpath(version_path.as_posix())
@ -347,7 +350,7 @@ def boot():
# Play animation
# ------------------------------------------------------------------------
from pype.lib.terminal_splash import 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):
@ -363,7 +366,6 @@ def boot():
# Determine mongodb connection
# ------------------------------------------------------------------------
pype_mongo = ""
try:
pype_mongo = _determine_mongodb()
except RuntimeError as e:
@ -385,7 +387,6 @@ def boot():
if getattr(sys, 'frozen', False):
# find versions of Pype to be used with frozen code
version_path = None
try:
version_path = _find_frozen_pype(use_version, use_staging)
except RuntimeError as e:
@ -399,6 +400,7 @@ def boot():
os.path.dirname(os.path.realpath(__file__)))
# get current version of Pype
local_version = bootstrap.get_local_live_version()
os.environ["PYPE_VERSION"] = local_version
if use_version and use_version != local_version:
pype_versions = bootstrap.find_pype(include_zips=True)
version_path = BootstrapRepos.get_version_path_from_list(
@ -406,7 +408,9 @@ def boot():
if version_path:
# use specified
bootstrap.add_paths_from_directory(version_path)
os.environ["PYPE_VERSION"] = use_version
else:
version_path = pype_root
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]
@ -434,6 +438,8 @@ def boot():
del sys.modules["pype.version"]
except AttributeError:
pass
except KeyError:
pass
from pype import cli
from pype.lib import terminal as t
@ -442,10 +448,9 @@ def boot():
set_modules_environments()
info = get_info()
info.insert(0, ">>> Using Pype from [ {} ]".format(
os.path.dirname(cli.__file__)))
info.insert(0, f">>> Using Pype from [ {version_path} ]")
t_width = os.get_terminal_size().columns
t_width = os.get_terminal_size().columns - 2
_header = f"*** Pype [{__version__}] "
info.insert(0, _header + "-" * (t_width - len(_header)))

View file

@ -33,7 +33,8 @@ def test_pype_version():
assert str(v3) == "1.2.3-staging"
v4 = PypeVersion(1, 2, 3, variant="staging", client="client")
assert str(v4) == "1.2.3-staging-client"
assert str(v4) == "1.2.3-client-staging"
assert v3 < v4
v5 = PypeVersion(1, 2, 3, variant="foo", client="x")
assert str(v5) == "1.2.3-x"
@ -100,7 +101,7 @@ def test_pype_version():
with pytest.raises(ValueError):
_ = PypeVersion(version="booobaa")
v11 = PypeVersion(version="4.6.7-staging-client")
v11 = PypeVersion(version="4.6.7-client-staging")
assert v11.major == 4
assert v11.minor == 6
assert v11.subversion == 7
@ -131,7 +132,7 @@ def test_search_string_for_pype_version(printer):
("foo-3.0", False),
("foo-3.0.1", True),
("3", False),
("foo-3.0.1-staging-client", True),
("foo-3.0.1-client-staging", True),
("foo-3.0.1-bar-baz", True)
]
for ver_string in strings:
@ -178,7 +179,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
suffix=".zip", type="zip", valid=True),
test_pype(prefix="bum-v", version="5.5.4-staging",
suffix=".zip", type="zip", valid=True),
test_pype(prefix="zum-v", version="5.5.5-staging-client",
test_pype(prefix="zum-v", version="5.5.5-client-staging",
suffix=".zip", type="zip", valid=True),
test_pype(prefix="fam-v", version="5.6.3",
suffix=".zip", type="zip", valid=True),
@ -201,7 +202,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
suffix=".zip", type="zip", valid=True),
test_pype(prefix="woo-v", version="7.2.8-client-strange",
suffix=".zip", type="zip", valid=True),
test_pype(prefix="loo-v", version="7.2.10-staging-client",
test_pype(prefix="loo-v", version="7.2.10-client-staging",
suffix=".zip", type="zip", valid=True),
test_pype(prefix="kok-v", version="7.0.1",
suffix=".zip", type="zip", valid=True)
@ -222,7 +223,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
suffix=".zip", type="zip", valid=True),
test_pype(prefix="foo-v", version="3.0.1-staging",
suffix=".zip", type="zip", valid=True),
test_pype(prefix="foo-v", version="3.0.1-staging-client",
test_pype(prefix="foo-v", version="3.0.1-client-staging",
suffix=".zip", type="zip", valid=True),
test_pype(prefix="foo-v", version="3.2.0",
suffix=".zip", type="zip", valid=True)
@ -254,8 +255,8 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
fp.write("invalid")
def _create_valid_dir(path: Path, version: str):
pype_path = path / "pype"
version_path = path / "pype" / "version.py"
pype_path = path / "pype" / "pype"
version_path = pype_path / "version.py"
pype_path.mkdir(parents=True, exist_ok=True)
with open(version_path, "w") as fp:
fp.write(f"__version__ = '{version}'\n\n")
@ -306,7 +307,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
_build_test_item(dir_path, test_file)
printer("testing finding Pype in given path ...")
result = fix_bootstrap.find_pype(g_path, True)
result = fix_bootstrap.find_pype(g_path, include_zips=True)
# we should have results as file were created
assert result is not None, "no Pype version found"
# latest item in `result` should be latest version found.
@ -317,6 +318,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
test_versions_3[3].suffix
)
)
assert result, "nothing found"
assert result[-1].path == expected_path, "not a latest version of Pype 3"
monkeypatch.setenv("PYPE_PATH", e_path.as_posix())
@ -332,6 +334,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
test_versions_1[5].suffix
)
)
assert result, "nothing found"
assert result[-1].path == expected_path, "not a latest version of Pype 1"
monkeypatch.delenv("PYPE_PATH", raising=False)
@ -350,14 +353,15 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
# latest item in `result` should be latest version found.
expected_path = Path(
d_path / "{}{}{}".format(
test_versions_2[4].prefix,
test_versions_2[4].version,
test_versions_2[4].suffix
test_versions_2[3].prefix,
test_versions_2[3].version,
test_versions_2[3].suffix
)
)
assert result, "nothing found"
assert result[-1].path == expected_path, "not a latest version of Pype 2"
result = fix_bootstrap.find_pype(e_path, True)
result = fix_bootstrap.find_pype(e_path, include_zips=True)
assert result is not None, "no Pype version found"
expected_path = Path(
e_path / "{}{}{}".format(
@ -368,12 +372,13 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
)
assert result[-1].path == expected_path, "not a latest version of Pype 1"
result = fix_bootstrap.find_pype(dir_path, True)
result = fix_bootstrap.find_pype(dir_path, include_zips=True)
assert result is not None, "no Pype versions found"
expected_path = Path(
e_path / "{}{}{}".format(
dir_path / "{}{}{}".format(
test_versions_4[0].prefix,
test_versions_4[0].version,
test_versions_4[0].suffix
)
)
assert result[-1].path == expected_path, "not a latest version of Pype 4"

View file

@ -168,6 +168,7 @@ catch {
Write-Host ">>> " -NoNewline -ForegroundColor green
Write-Host "Cleaning cache files ... " -NoNewline
Get-ChildItem $pype_root -Filter "*.pyc" -Force -Recurse | Remove-Item -Force
Get-ChildItem $pype_root -Filter "*.pyo" -Force -Recurse | Remove-Item -Force
Get-ChildItem $pype_root -Filter "__pycache__" -Force -Recurse | Remove-Item -Force -Recurse
Write-Host "OK" -ForegroundColor green
@ -176,6 +177,7 @@ Write-Host "Building Pype ..."
$out = & python setup.py build 2>&1
Set-Content -Path "$($pype_root)\build\build.log" -Value $out
& python -B "$($pype_root)\tools\build_dependencies.py"
Write-Host ">>> " -NoNewline -ForegroundColor green
Write-Host "deactivating venv ..."

119
tools/build_dependencies.py Normal file
View file

@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
"""Script to fix frozen dependencies.
Because Pype code needs to run under different versions of Python interpreter
(yes, even Python 2) we need to include all dependencies as source code
without Python's system stuff. Cx-freeze puts everything into lib and compile
it as .pyc/.pyo files and that doesn't work for hosts like Maya 2020 with
their own Python interpreter and libraries.
This script will take ``site-packages`` and copy them to built Pype under
``dependencies`` directory. It will then compare stuff inside with ``lib`` folder
in frozen Pype, removing duplicities from there.
This must be executed after build finished and it is done by build PowerShell
script.
Note: Speedcopy can be used for copying if server-side copy is important for
speed.
"""
import os
import sys
import site
from distutils.util import get_platform
from pathlib import Path
import shutil
import blessed
import time
term = blessed.Terminal()
def _print(msg: str, type: int = 0) -> None:
"""Print message to console.
Args:
msg (str): message to print
type (int): type of message (0 info, 1 error, 2 note)
"""
if type == 0:
header = term.aquamarine3(">>> ")
elif type == 1:
header = term.orangered2("!!! ")
elif type == 2:
header = term.tan1("... ")
else:
header = term.darkolivegreen3("--- ")
print("{}{}".format(header, msg))
_print("Starting dependency cleanup ...")
start_time = time.time_ns()
# path to venv site packages
sites = site.getsitepackages()
# WARNING: this assumes that all we've got is path to venv itself and
# another path ending with 'site-packages' as is default. But because
# this must run under different platform, we cannot easily check if this path
# is the one, because under Linux and macOS site-packages are in different
# location.
site_pkg = None
for s in sites:
site_pkg = Path(s)
if site_pkg.name == "site-packages":
break
_print("Getting venv site-packages ...")
assert site_pkg, "No venv site-packages are found."
_print(f"Working with: {site_pkg}", 2)
# now, copy it to build directory
build_dir = None
if sys.platform.startswith("linux"):
# TODO: what is it under linux?
raise NotImplementedError("not implemented for linux yet")
elif sys.platform == "darwin":
# TODO: what is it under macOS?
raise NotImplementedError("not implemented for macOS yet")
elif sys.platform == "win32":
# string is formatted as cx_freeze is doing it
build_dir = "exe.{}-{}".format(get_platform(), sys.version[0:3])
# create full path
build_dir = Path(os.path.dirname(__file__)).parent / "build" / build_dir
_print(f"Using build at {build_dir}", 2)
assert build_dir.exists(), "Build directory doesn't exist"
deps_dir = build_dir / "dependencies"
# copy all files
_print("Copying dependencies ...")
shutil.copytree(site_pkg.as_posix(), deps_dir.as_posix())
# iterate over frozen libs and create list to delete
libs_dir = build_dir / "lib"
to_delete = []
_print("Finding duplicates ...")
for d in libs_dir.iterdir():
if (deps_dir / d.name) in deps_dir.iterdir():
to_delete.append(d)
_print(f"found {d}", 3)
# delete duplicates
_print(f"Deleting {len(to_delete)} duplicates ...")
for d in to_delete:
if d.is_dir():
shutil.rmtree(d)
else:
d.unlink()
end_time = time.time_ns()
total_time = (end_time - start_time) / 1000000000
_print(f"Dependency cleanup done in {total_time} secs.")

View file

@ -90,7 +90,7 @@ Write-Host "OK [ $p ]" -ForegroundColor green
Write-Host ">>> " -NoNewline -ForegroundColor green
Write-Host "Entering venv ..."
try {
. (".\venv\Scripts\Activate.ps1")
. ("$($pype_root)\venv\Scripts\Activate.ps1")
}
catch {
Write-Host "!!! Failed to activate" -ForegroundColor red
@ -99,6 +99,9 @@ catch {
}
Write-Host ">>> " -NoNewline -ForegroundColor green
Write-Host "Generating zip from current sources ..."
Write-Host "... " -NoNewline -ForegroundColor Magenta
Write-Host "arguments: " -NoNewline -ForegroundColor Gray
Write-Host $ARGS -ForegroundColor White
& python "$($pype_root)\start.py" generate-zip $ARGS
Write-Host ">>> " -NoNewline -ForegroundColor green