import os
import re
import sys
import logging
import datetime
import traceback
_filename_ascii_strip_re = re.compile(r'[^-\w.]')
_windows_device_files = ('CON', 'AUX', 'COM1', 'COM2', 'COM3', 'COM4',
'LPT1', 'LPT2', 'LPT3', 'PRN', 'NUL')
def inrange(number, base, offset=0.5):
r"""Evaluate whether `number` is within `base` +- `offset`
Lower bound is *included* whereas upper bound is *excluded*
so as to allow for ranges to be stacked up against each other.
For example, an offset of 0.5 and a base of 1 evenly stacks
up against a base of 2 with identical offset.
Arguments:
number (float): Number to consider
base (float): Center of range
offset (float, optional): Amount of offset from base
Usage:
>>> inrange(0, base=1, offset=0.5)
False
>>> inrange(0.4, base=1, offset=0.5)
False
>>> inrange(1.4, base=1, offset=0.5)
True
>>> # Lower bound is included
>>> inrange(0.5, base=1, offset=0.5)
True
>>> # Upper bound is excluded
>>> inrange(1.5, base=1, offset=0.5)
False
"""
return (base - offset) <= number < (base + offset)
class MessageHandler(logging.Handler):
def __init__(self, records, *args, **kwargs):
# Not using super(), for compatibility with Python 2.6
logging.Handler.__init__(self, *args, **kwargs)
self.records = records
def emit(self, record):
self.records.append(record)
def extract_traceback(exception):
try:
exc_type, exc_value, exc_traceback = sys.exc_info()
exception.traceback = traceback.extract_tb(exc_traceback)[-1]
except:
pass
finally:
del(exc_type, exc_value, exc_traceback)
def time():
return datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")
class ItemList(list):
"""List with keys
Raises:
KeyError is item is not in list
Example:
>>> Obj = type("Object", (object,), {})
>>> obj = Obj()
>>> obj.name = "Test"
>>> l = ItemList(key="name")
>>> l.append(obj)
>>> l[0] == obj
True
>>> l["Test"] == obj
True
>>> try:
... l["NotInList"]
... except KeyError:
... print True
True
>>> obj == l.get("Test")
True
>>> l.get("NotInList") == None
True
"""
def __init__(self, key, object=list()):
super(ItemList, self).__init__(object)
self.key = key
def __getitem__(self, index):
if isinstance(index, int):
return super(ItemList, self).__getitem__(index)
for item in self:
if getattr(item, self.key) == index:
return item
raise KeyError("%s not in list" % index)
def get(self, key, default=None):
try:
return self.__getitem__(key)
except KeyError:
return default
class classproperty(object):
def __init__(self, getter):
self.getter = getter
def __get__(self, instance, owner):
return self.getter(owner)
[docs]def log(cls):
"""Decorator for attaching a logger to the class `cls`
Loggers inherit the syntax {module}.{submodule}
Example
>>> @log
... class MyClass(object):
... pass
>>>
>>> myclass = MyClass()
>>> myclass.log.info('Hello World')
"""
module = cls.__module__
name = cls.__name__
# Package name appended, for filtering of LogRecord instances
logname = "pyblish.%s.%s" % (module, name)
cls.log = logging.getLogger(logname)
# All messages are handled by root-logger
cls.log.propagate = True
return cls
def parse_environment_paths(paths):
"""Given a (semi-)colon separated string of paths, return a list
Example:
>>> import os
>>> parse_environment_paths("path1" + os.pathsep + "path2")
['path1', 'path2']
>>> parse_environment_paths("path1" + os.pathsep)
['path1', '']
Arguments:
paths (str): Colon or semi-colon (depending on platform)
separated string of paths.
Returns:
list of paths as string.
"""
paths_list = list()
for path in paths.split(os.pathsep):
paths_list.append(path)
return paths_list
def format_filename2(filename):
"""Convert arbitrary string to valid filename, werkzeug-style.
Modified from werkzeug.utils.secure_filename()
Pass it a filename and it will return a secure version of it. This
filename can then safely be stored on a regular file system and passed
to :func:`os.path.join`. The filename returned is an ASCII only string
for maximum portability.
On windows system the function also makes sure that the file is not
named after one of the special device files.
Arguments:
filename (str): the filename to secure
Usage:
>>> format_filename2("john's portrait in 2004.jpg")
'johns_portrait_in_2004.jpg'
>>> format_filename2("something^_SD.dda.//fd/ad.exe")
'something_SD.dda._fd_ad.exe'
>>> format_filename2("Napoleon_:namespaces_GRP|group1_GRP")
'Napoleon_namespaces_GRPgroup1_GRP'
.. warning:: The function might return an empty filename. It's your
responsibility to ensure that the filename is unique and that you
generate random filename if the function returned an empty one.
.. versionadded:: 1.0.9
"""
if isinstance(filename, unicode):
from unicodedata import normalize
filename = normalize('NFKD', filename).encode('ascii', 'ignore')
for sep in os.path.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, ' ')
filename = str(_filename_ascii_strip_re.sub('', '_'.join(
filename.split()))).strip('._')
# on nt a couple of special files are present in each folder. We
# have to ensure that the target file is not such a filename. In
# this case we prepend an underline
if os.name == 'nt' and filename and \
filename.split('.')[0].upper() in _windows_device_files:
filename = '_' + filename
return filename
def get_formatter():
"""Return a default Pyblish formatter for logging
Example:
>>> import logging
>>> log = logging.getLogger("myLogger")
>>> handler = logging.StreamHandler()
>>> handler.setFormatter(get_formatter())
"""
formatter = logging.Formatter(
'%(asctime)s - '
'%(levelname)s - '
'%(name)s - '
'%(message)s',
'%H:%M:%S')
return formatter
def setup_log(root='pyblish', level=logging.DEBUG):
"""Setup a default logger for Pyblish
Example:
>>> log = setup_log()
>>> log.info("Hello, World")
"""
formatter = logging.Formatter("%(levelname)s - %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(formatter)
log = logging.getLogger(root)
log.propagate = True
log.handlers[:] = []
log.addHandler(handler)
log.setLevel(level)
return log
def main_package_path():
"""Return path of main pyblish package"""
lib_py_path = sys.modules[__name__].__file__
package_path = os.path.dirname(lib_py_path)
return package_path
def import_module(name, package=None):
"""Import a module
The 'package' argument is required when performing a relative import. It
specifies the package to use as the anchor point from which to resolve the
relative import to an absolute import.
"""
def _resolve_name(name, package, level):
"""Return the absolute name of the module to be imported."""
if not hasattr(package, 'rindex'):
raise ValueError("'package' not set to a string")
dot = len(package)
for x in xrange(level, 1, -1):
try:
dot = package.rindex('.', 0, dot)
except ValueError:
raise ValueError("attempted relative import beyond top-level "
"package")
return "%s.%s" % (package[:dot], name)
if name.startswith('.'):
if not package:
raise TypeError("relative imports require the 'package' argument")
level = 0
for character in name:
if character != '.':
break
level += 1
name = _resolve_name(name[level:], package, level)
__import__(name)
return sys.modules[name]