Skip to content

Commit

Permalink
Merge pull request #2778 from boegel/py3_wrapper_fixes
Browse files Browse the repository at this point in the history
fix running of 'easyconfigparser' and 'format_convert' test suites with Python 3
  • Loading branch information
bartoldeman committed Feb 21, 2019
2 parents 420706a + 6687b20 commit 24dea4e
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 370 deletions.
44 changes: 24 additions & 20 deletions easybuild/base/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@
Original code by http://stackoverflow.com/users/416467/kindall from answer 4 of
http://stackoverflow.com/questions/9057669/how-can-i-intercept-calls-to-pythons-magic-methods-in-new-style-classes
"""
from easybuild.tools.py2vs3 import mk_wrapper_baseclass


class Wrapper(object):
"""Wrapper class that provides proxy access to an instance of some
internal instance."""
class WrapperMeta(type):
"""Metaclass for type wrappers."""

def __init__(cls, name, bases, dct):

def make_proxy(name):
def proxy(self, *args): # pylint:disable=unused-argument
return getattr(self._obj, name)
return proxy

# create proxies for wrapped object's double-underscore attributes
type.__init__(cls, name, bases, dct)
if cls.__wraps__:
ignore = set("__%s__" % n for n in cls.__ignore__.split())
for name in dir(cls.__wraps__):
if name.startswith("__"):
if name not in ignore and name not in dct:
setattr(cls, name, property(make_proxy(name)))


class Wrapper(mk_wrapper_baseclass(WrapperMeta)):
"""
Wrapper class that provides proxy access to an instance of some internal instance.
"""
__wraps__ = None
__ignore__ = "class mro new init setattr getattr getattribute"

Expand All @@ -26,20 +47,3 @@ def __init__(self, obj):
# provide proxy access to regular attributes of wrapped object
def __getattr__(self, name):
return getattr(self._obj, name)

# create proxies for wrapped object's double-underscore attributes
class __metaclass__(type):
def __init__(cls, name, bases, dct):

def make_proxy(name):
def proxy(self, *args): # pylint:disable=unused-argument
return getattr(self._obj, name)
return proxy

type.__init__(cls, name, bases, dct)
if cls.__wraps__:
ignore = set("__%s__" % n for n in cls.__ignore__.split())
for name in dir(cls.__wraps__):
if name.startswith("__"):
if name not in ignore and name not in dct:
setattr(cls, name, property(make_proxy(name)))
33 changes: 1 addition & 32 deletions easybuild/framework/easyconfig/format/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,7 @@
:author: Stijn De Weirdt (Ghent University)
"""
from easybuild.framework.easyconfig.format.version import VersionOperator, ToolchainVersionOperator
from easybuild.tools.convert import Convert, DictOfStrings, ListOfStrings


class Patch(DictOfStrings):
"""Handle single patch"""
ALLOWED_KEYS = ['level', 'dest']
KEYLESS_ENTRIES = ['filename'] # filename as first element (also filename:some_path is supported)
# explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined
__str__ = DictOfStrings.__str__

def _from_string(self, txt):
"""Convert from string
# shorthand
filename;level:<int>;dest:<string> -> {'filename': filename, 'level': level, 'dest': dest}
# full dict notation
filename:filename;level:<int>;dest:<string> -> {'filename': filename, 'level': level, 'dest': dest}
"""
res = DictOfStrings._from_string(self, txt)
if 'level' in res:
res['level'] = int(res['level'])
return res


class Patches(ListOfStrings):
"""Handle patches as list of Patch"""
# explicit definition of __str__ is required for unknown reason related to the way Wrapper is defined
__str__ = ListOfStrings.__str__

def _from_string(self, txt):
"""Convert from comma-separated string"""
res = ListOfStrings._from_string(self, txt)
return [Patch(x) for x in res]
from easybuild.tools.convert import Convert


class Dependency(Convert):
Expand Down
151 changes: 0 additions & 151 deletions easybuild/tools/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,11 @@
from easybuild.base.wrapper import Wrapper
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.py2vs3 import string_type
from easybuild.tools.utilities import get_subclasses, nub


_log = fancylogger.getLogger('tools.convert', fname=False)


class AllowedValueError(ValueError):
"""Specific type of error for non-allowed keys in DictOf classes."""
pass


class Convert(Wrapper):
"""
Convert casts a string passed via the initialisation to a Convert (sub)class instance,
Expand Down Expand Up @@ -81,148 +75,3 @@ def _from_string(self, txt):
def __str__(self):
"""Convert to string"""
raise NotImplementedError


class ListOfStrings(Convert):
"""Convert str to list of strings"""
SEPARATOR_LIST = ','
__wraps__ = list

def __init__(self, obj, separator_list=None):
self.separator_list = separator_list
if self.separator_list is None:
self.separator_list = self.SEPARATOR_LIST
super(ListOfStrings, self).__init__(obj)

def _from_string(self, txt):
"""Parse string as a list of strings.
For example: "a,b" -> ['a', 'b']
"""
return self._split_string(txt, sep=self.separator_list)

def __str__(self):
"""Convert to string. str() is used for easy subclassing"""
return self.SEPARATOR_LIST.join([str(x) for x in self])


class DictOfStrings(Convert):
"""Convert string to dictionary with string values
key/value pairs are separated via SEPARATOR_DICT
key and value are separated via SEPARATOR_KEY_VALUE
"""
SEPARATOR_DICT = ';'
SEPARATOR_KEY_VALUE = ':'
ALLOWED_KEYS = None
KEYLESS_ENTRIES = []
__wraps__ = dict

def __init__(self, obj, separator_dict=None, separator_key_value=None, allowed_keys=None, raise_allowed=False):
self.separator_dict = separator_dict
if self.separator_dict is None:
self.separator_dict = self.SEPARATOR_DICT
self.separator_key_value = separator_key_value
if self.separator_key_value is None:
self.separator_key_value = self.SEPARATOR_KEY_VALUE
self.allowed_keys = allowed_keys
if self.allowed_keys is None:
self.allowed_keys = self.ALLOWED_KEYS
self.raise_allowed = ValueError
if raise_allowed:
self.raise_allowed = AllowedValueError

super(DictOfStrings, self).__init__(obj)

def _from_string(self, txt):
"""Parse string as a dictionary of /with string values.
For example: "a:b;c:d" -> {'a':'b', 'c':'d'}"
It also supports automagic dictionary creation via the KEYLESS_ENTRIES list of keys,
but the order is important.
KEYLESS_ENTRIES=['first','second']
will convert
"val1;val2;third:val3" -> {'first': 'val1', 'second': 'val2', 'third': 'val3'}
"""

res = {}
ke_usage = []
for idx, entry in enumerate(self._split_string(txt, sep=self.separator_dict)):
key_value = self._split_string(entry, sep=self.separator_key_value, max=1)
if len(key_value) == 2:
key, value = key_value
if self.allowed_keys is None or key in self.allowed_keys:
res[key] = value
else:
raise self.raise_allowed('Unsupported key %s (allowed %s)' % (key, self.allowed_keys))
elif idx + 1 <= len(self.KEYLESS_ENTRIES):
# auto-complete list into dict
# only valid if all previous keyless entries were processed before and in order
if ke_usage == range(idx):
# all elements have to used before this one
ke_usage.append(idx)
res[self.KEYLESS_ENTRIES[idx]] = entry
else:
msg = 'Unsupported element %s (previous keyless entries %s, current idx %s)'
raise ValueError(msg % (key_value, ke_usage, idx))

else:
msg = 'Unsupported element %s (from entry %s, missing key_value separator %s)'
raise ValueError(msg % (key_value, entry, self.separator_key_value))
return res

def __str__(self):
"""Convert to string"""
# the str conversions are needed for subclasses that use non-string values
keyless_entries = [str(self[ml]) for ml in self.KEYLESS_ENTRIES if ml in self]

def join_item(item):
return self.separator_key_value.join([item[0], str(item[1])])
regular = [join_item(it) for it in self.items() if not it[0] in self.KEYLESS_ENTRIES]
return self.separator_dict.join(keyless_entries + regular)


class ListOfStringsAndDictOfStrings(Convert):
"""Returns a list of strings and with last element a dict"""
SEPARATOR_LIST = ','
SEPARATOR_DICT = ';'
SEPARATOR_KEY_VALUE = ':'
ALLOWED_KEYS = None
__wraps__ = list

def _from_string(self, txt):
"""Parse string as a list of strings, followed by a dictionary of strings at the end.
For example, "a,b,c:d;e:f,g,h,i:j" -> ['a','b',{'c':'d', 'e': 'f'}, 'g', 'h', {'i': 'j'}]
"""
res = []

for element in ListOfStrings(txt, separator_list=self.SEPARATOR_LIST):
try:
kwargs = {
'separator_dict': self.SEPARATOR_DICT,
'separator_key_value': self.SEPARATOR_KEY_VALUE,
'allowed_keys': self.ALLOWED_KEYS,
'raise_allowed': True,
}
res.append(DictOfStrings(element, **kwargs))
except AllowedValueError as msg:
# reraise it as regular ValueError
raise ValueError(str(msg))
except ValueError as msg:
# ValueError because the string can't be converted to DictOfStrings
# assuming regular string value
self.log.debug('ValueError catched with message %s, treat as regular string.' % msg)
res.append(element)

return res

def __str__(self):
"""Return string"""
return self.SEPARATOR_LIST.join([str(x) for x in self])


def get_convert_class(class_name):
"""Return the Convert class with specified class name class_name"""
res = [x for x in nub(get_subclasses(Convert)) if x.__name__ == class_name]
if len(res) == 1:
return res[0]
else:
raise EasyBuildError("More than one Convert subclass found for name %s: %s", class_name, res)
12 changes: 12 additions & 0 deletions easybuild/tools/py2vs3/py2.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,15 @@ def raise_with_traceback(exception_class, message, traceback):
def extract_method_name(method_func):
"""Extract method name from lambda function."""
return '_'.join(method_func.func_code.co_names)


def mk_wrapper_baseclass(metaclass):

class WrapperBase(object):
"""
Wrapper class that provides proxy access to an instance of some internal instance.
"""
__metaclass__ = metaclass
__wraps__ = None

return WrapperBase
11 changes: 11 additions & 0 deletions easybuild/tools/py2vs3/py3.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,14 @@ def raise_with_traceback(exception_class, message, traceback):
def extract_method_name(method_func):
"""Extract method name from lambda function."""
return '_'.join(method_func.__code__.co_names)


def mk_wrapper_baseclass(metaclass):

class WrapperBase(object, metaclass=metaclass):
"""
Wrapper class that provides proxy access to an instance of some internal instance.
"""
__wraps__ = None

return WrapperBase

0 comments on commit 24dea4e

Please sign in to comment.