#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''A collection of functions that are used by modules in this package.
This module is not likely to change often.
'''
# IMPORT STANDARD LIBRARIES
# scspell-id: 3c62e4aa-c280-11e7-be2b-382c4ac59cfd
import os
import string
import functools
import itertools
# IMPORT THIRD-PARTY LIBRARIES
import six
# IMPORT LOCAL LIBRARIES
from ..core import check
HIERARCHY_SEP = '/'
DEFAULT_ASSIGNMENT = 'master'
FAILURE_KEY = 'failed'
SUCCESS_KEY = 'success'
ENVIRONMENT_FAILURE_KEY = 'environment_failure'
IMPORT_FAILURE_KEY = 'import_failure'
LOAD_FAILURE_KEY = 'load_failure'
NOT_CALLABLE_KEY = 'not_callable'
PLATFORM_FAILURE_KEY = 'platform_failure'
RESOLUTION_FAILURE_KEY = 'resolution_failure'
DESCRIPTORS_ENV_VAR = 'WAYS_DESCRIPTORS'
PARSERS_ENV_VAR = 'WAYS_PARSERS'
PLATFORM_ENV_VAR = 'WAYS_PLATFORM'
PLATFORMS_ENV_VAR = 'WAYS_PLATFORMS'
PLUGINS_ENV_VAR = 'WAYS_PLUGINS'
PRIORITY_ENV_VAR = 'WAYS_PRIORITY'
PARENT_TOKEN = '{root}'
WAYS_UUID_KEY = 'uuid'
[docs]def expand_string(format_string, obj):
'''Split a string into a dict using a Python-format string.
Warning:
Format-strings that have two tokens side-by-side are invalid.
They must have at least some character between them.
This format_string is invalid '{NAME}{ID}',
this format_string is valid '{NAME}_{ID}'.
Example:
>>> shot = 'NAME_010'
>>> format_string = '{SHOT}_{ID}'
>>> expand_string(format_string, shot)
... {'SHOT': 'NAME', 'ID': '010'}
Args:
format_string (str): The Python-format style string to use to split it.
obj (str): The string to split out into a dict.
Raises:
ValueError: If the format_string given is invalid.
Returns:
dict: The created dict from our obj string.
'''
if '}{' in format_string:
raise ValueError('format_string: "{temp_}" was invalid. Curly braces, '
'cannot be used, back to back.'.format(temp_=format_string))
info = dict()
# The string is reversed and processed from end to beginning
for prefix, field, _, _ in reversed(list(string.Formatter().parse(format_string))):
if not prefix:
# We got to the beginning of the formatted str so just return obj
info[field] = obj
continue
try:
remainder, value = obj.rsplit(prefix, 1)
except ValueError:
# If this block runs it means that there was a bad match between
# the formatted_string and obj.
#
# Example:
# >>> text = '/jobs/some_job/some_kind/of/real_folders'
# >>> pattern = '/jobs/{JOB}/some_kind/of/real_folders/inner'
# >>> expand_string(pattern, text) # Raises ValueError
#
# To prevent false positives, we'll return an empty dict
#
return dict()
if field:
info[field] = value
obj = remainder
return info
[docs]def get_python_files(item):
'''Get the Python files at some file or directory.
Note:
If the given item is a Python file, just return it.
Args:
item (str): The absolute path to a file or folder.
Returns:
list[str]: The Python files at the given location.
'''
if os.path.isfile(item):
return [item]
elif os.path.isdir(item):
files = []
for item_ in os.listdir(item):
item_ = os.path.join(item, item_)
if os.path.isfile(item_) and item_.lower().endswith(('.pyc', '.py')):
files.append(item_)
return files
return []
[docs]def split_into_parts(obj, split, as_type=tuple):
'''Split a string-like object into parts, using some split variable.
Example:
>>> path = 'some/thing'
>>> split_into_parts(path, split='/')
... ('some', 'thing')
Args:
obj (str or iterable): The object to split.
split (str): The character(s) to split obj by.
as_type (:obj:`callable[iterable[str]]`, optional):
The type to return from this function. Default: tuple.
Returns:
as_type[str]: The split pieces.
'''
obj = check.force_itertype(obj)
obj = (part.strip() for obj_ in obj for part in obj_.split(split))
return as_type(part for part in obj if part)
[docs]def split_hierarchy(obj, as_type=tuple):
'''Split a hierarchy into pieces, using the "/" character.
Args:
obj (str or tuple[str]):
The hierarchy to split.
as_type (:obj:`callable`, optional):
The iterable type to return the hierarchy.
Returns:
tuple[str]:
The hierarchy, split into pieces.
'''
try:
if obj[0] == HIERARCHY_SEP:
items = itertools.chain(
[HIERARCHY_SEP], split_into_parts(obj, split=HIERARCHY_SEP, as_type=list))
return tuple(items)
except IndexError:
pass
return split_into_parts(obj, split=HIERARCHY_SEP, as_type=as_type)
# pylint: disable=invalid-name
split_by_comma = functools.partial(split_into_parts, split=',')
[docs]def import_object(name):
'''Import a object of any kind, as long as it is on the PYTHONPATH.
Args:
name (str): An import name (Example: 'ways.api.Plugin')
Raises:
ImportError: If some object down the name chain was not importable or
if the entire name could not be found in the PYTHONPATH.
Returns:
The imported module, classobj, or callable function, or object.
'''
components = name.split('.')
module = __import__(components[0])
for comp in components[1:]:
module = getattr(module, comp)
return module
[docs]def memoize(function):
'''Create cache of values for a function.'''
memo = {}
def wrapper(*args):
'''Run the original function and store its output, given some args.'''
if args in memo:
return memo[args]
value = function(*args)
memo[args] = value
return value
return wrapper
[docs]def decode(obj):
'''dict[str]: Convert a URL-encoded string back into a dict.'''
return conform_decode(six.moves.urllib.parse.parse_qs(obj))
[docs]def encode(obj):
'''Make the given descriptor information into a standard URL encoding.
Args:
obj (dict[str]): The Descriptor information to serialize.
This is normally something like
{'create_using': ways.api.FolderDescriptor}.
Returns:
str: The output encoding.
'''
# pylint: disable=redundant-keyword-arg
return six.moves.urllib.parse.urlencode(obj, doseq=True)