def test_cls(self): x = ["a"] y = listify(x) self.assertEqual(x, y) self.assertTrue(x is y) x.append("b") self.assertEqual(x, y) x = ["a"] y = listify(x, cls=None) self.assertEqual(x, y) self.assertTrue(x is y) x.append("b") self.assertEqual(x, y) class sublist(list): pass x = ["a"] y = listify(x, cls=sublist) self.assertEqual(x, y) self.assertFalse(x is y) x.append("b") self.assertNotEqual(x, y) x = ["a"] y = listify(x, cls=deque) self.assertEqual(x, list(y)) self.assertEqual(len(x), len(y)) self.assertFalse(x is y) x.append("b") self.assertNotEqual(x, list(y)) self.assertNotEqual(len(x), len(y))
def test_cls(self): x = ['a'] y = listify(x) assert x == y assert x is y x.append('b') assert x == y x = ['a'] y = listify(x, cls=None) assert x == y assert x is y x.append('b') assert x == y class sublist(list): pass x = ['a'] y = listify(x, cls=sublist) assert isinstance(y, sublist) assert x == y assert x is not y x.append('b') assert x != y x = ['a'] y = listify(x, cls=deque) assert isinstance(y, deque) assert x == list(y) assert len(x) == len(y) assert x is not y x.append('b') assert x != list(y) assert len(x) != len(y)
def test_default(self): assert [] == listify([], default=None) assert ['a'] == listify(['a'], minlen=1, default=None) assert ['a', None] == listify(['a'], minlen=2, default=None) assert ['a', None, None] == listify(['a'], minlen=3, default=None) assert [] == listify([], default='XXX') assert ['a'] == listify(['a'], minlen=1, default='XXX') assert ['a', 'XXX'] == listify(['a'], minlen=2, default='XXX') assert ['a', 'XXX', 'XXX'] == listify(['a'], minlen=3, default='XXX')
def test_default(self): self.assertEqual([], listify([], default=None)) self.assertEqual(["a"], listify(["a"], minlen=1, default=None)) self.assertEqual(["a", None], listify(["a"], minlen=2, default=None)) self.assertEqual(["a", None, None], listify(["a"], minlen=3, default=None)) self.assertEqual([], listify([], default="XXX")) self.assertEqual(["a"], listify(["a"], minlen=1, default="XXX")) self.assertEqual(["a", "XXX"], listify(["a"], minlen=2, default="XXX")) self.assertEqual(["a", "XXX", "XXX"], listify(["a"], minlen=3, default="XXX"))
def collect_superclasses(cls, terminal_class=None, modules=None): """ Recursively collects all ancestor superclasses in the inheritance hierarchy of the given class, including the class itself. Note: Inlcudes `cls` itself. Will not include `terminal_class`. Args: cls (class): The class object from which the collection should begin. terminal_class (class or list): If `terminal_class` is encountered in the hierarchy, we stop ascending the tree. `terminal_class` will not be included in the returned list. modules (string, module, or list): If `modules` is passed, we only return classes that are in the given module/modules. This can be used to exclude base classes that come from external libraries. Returns: list: A list of `class` objects from which `cls` inherits. This list will include `cls` itself. """ terminal_class = listify(terminal_class) if modules is not None: modules = listify(modules) module_strings = [] for m in modules: if isinstance(m, six.string_types): module_strings.append(m) else: module_strings.append(m.__name__) modules = module_strings superclasses = set() is_in_module = modules is None or cls.__module__ in modules if is_in_module and cls not in terminal_class: superclasses.add(cls) for base in cls.__bases__: superclasses.update( collect_superclasses(base, terminal_class, modules)) return list(superclasses)
def argmod(*args): """ Decorator that intercepts and modifies function arguments. Args: from_param (str|list): A parameter or list of possible parameters that should be modified using `modifier_func`. Passing a list of possible parameters is useful when a function's parameter names have changed, but you still want to support the old parameter names. to_param (str): Optional. If given, to_param will be used as the parameter name for the modified argument. If not given, to_param will default to the last parameter given in `from_param`. modifier_func (callable): The function used to modify the `from_param`. Returns: function: A function that modifies the given `from_param` before the function is called. """ from_param = listify(args[0]) to_param = from_param[-1] if len(args) < 3 else args[1] modifier_func = args[-1] def _decorator(func): try: argspec = inspect.getfullargspec(unwrap(func)) except AttributeError: argspec = inspect.getargspec(unwrap(func)) if to_param not in argspec.args: return func arg_index = argspec.args.index(to_param) @wraps(func) def _modifier(*args, **kwargs): kwarg = False for arg in from_param: if arg in kwargs: kwarg = arg break if kwarg: kwargs[to_param] = modifier_func(kwargs.pop(kwarg)) elif arg_index < len(args): args = list(args) args[arg_index] = modifier_func(args[arg_index]) return func(*args, **kwargs) return _modifier return _decorator
def test_list_identity(self): x = ['a'] y = listify(x) assert x == y assert x is y x.append('b') assert x == y class sublist(list): pass x = sublist('a') y = listify(x) assert x == y assert x is y x.append('b') assert x == y x = sublist('a') y = listify(x, cls=list) assert x == y assert x is y x.append('b') assert x == y
def test_list_identity(self): x = ["a"] y = listify(x) self.assertEqual(x, y) self.assertTrue(x is y) x.append("b") self.assertEqual(x, y) class sublist(list): pass x = sublist("a") y = listify(x) self.assertEqual(x, y) self.assertTrue(x is y) x.append("b") self.assertEqual(x, y) x = sublist("a") y = listify(x, cls=list) self.assertEqual(x, y) self.assertTrue(x is y) x.append("b") self.assertEqual(x, y)
def splitify(value, separator=",", strip=True, include_empty=False): """ Convert a value to a list using a supercharged `split()`. If `value` is a string, it is split by `separator`. If `separator` is `None` or empty, no attempt to split is made, and `value` is returned as the only item in a list. If `strip` is `True`, then the split strings will be stripped of whitespace. If `strip` is a string, then the split strings will be stripped of the given string. If `include_empty` is `False`, then empty split strings will not be included in the returned list. If `value` is `None` an empty list is returned. If `value` is already "listy", it is returned as-is. If `value` is any other type, it is returned as the only item in a list. >>> splitify("first item, second item") ['first item', 'second item'] >>> splitify("first path: second path: :skipped empty path", ":") ['first path', 'second path', 'skipped empty path'] >>> splitify(["already", "split"]) ['already', 'split'] >>> splitify(None) [] >>> splitify(1969) [1969] """ if is_listy(value): return value if isinstance(value, str) and separator: parts = value.split(separator) if strip: strip = None if strip is True else strip parts = [s.strip(strip) for s in parts] return [s for s in parts if include_empty or s] return listify(value)
def test_tuple(self): assert [] == listify(tuple()) assert ['a'] == listify(tuple('a')) assert ['a', 'b'] == listify(tuple(['a', 'b']))
def test_string(self): assert [''] == listify('') assert ['a'] == listify('a') assert ['ab'] == listify('ab')
def test_set(self): assert [] == listify(set()) assert ['a'] == listify(set('a')) assert ['a', 'b'] == sorted(listify(set(['a', 'b'])))
def test_falsey(self): assert [0] == listify(0) assert [0] == listify(0, minlen=0) assert [0] == listify(0, minlen=1) assert [0, None] == listify(0, minlen=2) assert [''] == listify('') assert [''] == listify('', minlen=0) assert [''] == listify('', minlen=1) assert ['', None] == listify('', minlen=2) assert [] == listify([]) assert [] == listify([], minlen=0) assert [None] == listify([], minlen=1) assert [None, None] == listify([], minlen=2)
def test_none(self): assert [] == listify(None) assert [] == listify(None, minlen=0) assert [None] == listify(None, minlen=1) assert [None, None] == listify(None, minlen=2)
def test_set(self): self.assertEqual([], listify(set())) self.assertEqual(["a"], listify(set("a"))) self.assertEqual(["a", "b"], sorted(listify(set(["a", "b"]))))
def test_list(self): self.assertEqual([], listify([])) self.assertEqual(["a"], listify(["a"])) self.assertEqual(["a", "b"], listify(["a", "b"]))
def test_dict(self): assert [{}] == listify({}) assert [{'a': 'A'}] == listify({'a': 'A'}) assert [{'a': 'A', 'b': 'B'}] == listify({'a': 'A', 'b': 'B'})
def test_string(self): self.assertEqual([""], listify("")) self.assertEqual(["a"], listify("a")) self.assertEqual(["ab"], listify("ab"))
def test_object(self): a = object() self.assertEqual([a], listify(a))
def test_dict(self): self.assertEqual([{}], listify({})) self.assertEqual([{"a": "A"}], listify({"a": "A"})) self.assertEqual([{"a": "A", "b": "B"}], listify({"a": "A", "b": "B"}))
def test_list(self): assert [] == listify([]) assert ['a'] == listify(['a']) assert ['a', 'b'] == listify(['a', 'b'])
def test_falsey(self): self.assertEqual([0], listify(0)) self.assertEqual([0], listify(0, minlen=0)) self.assertEqual([0], listify(0, minlen=1)) self.assertEqual([0, None], listify(0, minlen=2)) self.assertEqual([""], listify("")) self.assertEqual([""], listify("", minlen=0)) self.assertEqual([""], listify("", minlen=1)) self.assertEqual(["", None], listify("", minlen=2)) self.assertEqual([], listify([])) self.assertEqual([], listify([], minlen=0)) self.assertEqual([None], listify([], minlen=1)) self.assertEqual([None, None], listify([], minlen=2))
def test_minlen(self): self.assertEqual([], listify([])) self.assertEqual([], listify([], minlen=None)) self.assertEqual([], listify([], minlen=-1)) self.assertEqual([], listify([], minlen=0)) self.assertEqual([None], listify([], minlen=1)) self.assertEqual([None, None], listify([], minlen=2)) self.assertEqual([None, None, None], listify([], minlen=3)) self.assertEqual(["a"], listify(["a"])) self.assertEqual(["a"], listify(["a"], minlen=None)) self.assertEqual(["a"], listify(["a"], minlen=-1)) self.assertEqual(["a"], listify(["a"], minlen=0)) self.assertEqual(["a"], listify(["a"], minlen=1)) self.assertEqual(["a", None], listify(["a"], minlen=2)) self.assertEqual(["a", None, None], listify(["a"], minlen=3)) self.assertEqual(["a", "b"], listify(["a", "b"])) self.assertEqual(["a", "b"], listify(["a", "b"], minlen=None)) self.assertEqual(["a", "b"], listify(["a", "b"], minlen=-1)) self.assertEqual(["a", "b"], listify(["a", "b"], minlen=0)) self.assertEqual(["a", "b"], listify(["a", "b"], minlen=1)) self.assertEqual(["a", "b"], listify(["a", "b"], minlen=2)) self.assertEqual(["a", "b", None], listify(["a", "b"], minlen=3)) self.assertEqual(["a", "b", None, None], listify(["a", "b"], minlen=4))
def test_tuple(self): self.assertEqual([], listify(tuple())) self.assertEqual(["a"], listify(tuple("a"))) self.assertEqual(["a", "b"], listify(tuple(["a", "b"])))
def test_object(self): a = object() assert [a] == listify(a)
def resolve(name, modules=None): """Resolve a dotted name to an object (usually class, module, or function). If `name` is a string, attempt to resolve it according to Python dot notation, e.g. "path.to.MyClass". If `name` is anything other than a string, return it immediately: >>> resolve("calendar.TextCalendar") <class 'calendar.TextCalendar'> >>> resolve(object()) #doctest: +ELLIPSIS <object object at 0x...> If `modules` is specified, then resolution of `name` is restricted to the given modules. Leading dots are allowed in `name`, but they are ignored. Resolution **will not** traverse up the module path if `modules` is specified. If `modules` is not specified and `name` has leading dots, then resolution is first attempted relative to the calling function's module, and then absolutely. Resolution **will** traverse up the module path. If `name` has no leading dots, resolution is first attempted absolutely and then relative to the calling module. Warning: Do not resolve strings supplied by an end user without specifying `modules`. Instantiating an arbitrary object specified by an end user can introduce a potential security risk. To avoid this, restrict the search path by explicitly specifying `modules`. Restricting `name` resolution to a set of `modules`: >>> resolve("pockets.camel") #doctest: +ELLIPSIS <function camel at 0x...> >>> resolve("pockets.camel", modules=["re", "six"]) #doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Unable to resolve 'pockets.camel' in modules: ['re', 'six'] Args: name (str or object): A dotted name. modules (str or list, optional): A module or list of modules, under which to search for `name`. Returns: object: The object specified by `name`. Raises: ValueError: If `name` can't be resolved. """ if not isinstance(name, string_types): return name obj_path = name.split('.') search_paths = [] if modules: while not obj_path[0]: obj_path.pop(0) for module_path in listify(modules): search_paths.append(module_path.split('.') + obj_path) else: caller = inspect.getouterframes(inspect.currentframe())[1][0].f_globals module_path = caller['__name__'].split('.') if not obj_path[0]: obj_path.pop(0) while not obj_path[0]: obj_path.pop(0) if module_path: module_path.pop() search_paths.append(module_path + obj_path) search_paths.append(obj_path) else: search_paths.append(obj_path) search_paths.append(module_path + obj_path) for path in search_paths: try: obj = functools.reduce(getattr, path[1:], __import__(path[0])) except (AttributeError, ImportError): pass else: return obj raise ValueError("Unable to resolve '{0}' " "in modules: {1}".format(name, modules))
def camel(s, sep="_", lower_initial=False, upper_segments=None, preserve_upper=False): """Convert underscore_separated string (aka snake_case) to CamelCase. Works on full sentences as well as individual words: >>> camel("hello_world!") 'HelloWorld!' >>> camel("Totally works as_expected, even_with_whitespace!") 'Totally Works AsExpected, EvenWithWhitespace!' Args: sep (string, optional): Delineates segments of `s` that will be CamelCased. Defaults to an underscore "_". For example, if you want to CamelCase a dash separated word: >>> camel("xml-http-request", sep="-") 'XmlHttpRequest' lower_initial (bool, int, or list, optional): If True, the initial character of each camelCased word will be lowercase. If False, the initial character of each CamelCased word will be uppercase. Defaults to False: >>> camel("http_request http_response") 'HttpRequest HttpResponse' >>> camel("http_request http_response", lower_initial=True) 'httpRequest httpResponse' Optionally, `lower_initial` can be an int or a list of ints, indicating which individual segments of each CamelCased word should start with a lowercase. Supports negative numbers to index segments from the right: >>> camel("xml_http_request", lower_initial=0) 'xmlHttpRequest' >>> camel("xml_http_request", lower_initial=-1) 'XmlHttprequest' >>> camel("xml_http_request", lower_initial=[0, 1]) 'xmlhttpRequest' upper_segments (int or list, optional): Indicates which segments of CamelCased words should be fully uppercased, instead of just capitalizing the first letter. Can be an int, indicating a single segment, or a list of ints, indicating multiple segments. Supports negative numbers to index segments from the right. `upper_segments` is helpful when dealing with acronyms: >>> camel("tcp_socket_id", upper_segments=0) 'TCPSocketId' >>> camel("tcp_socket_id", upper_segments=[0, -1]) 'TCPSocketID' >>> camel("tcp_socket_id", upper_segments=[0, -1], lower_initial=1) 'TCPsocketID' preserve_upper (bool): If True, existing uppercase characters will not be automatically lowercased. Defaults to False. >>> camel("xml_HTTP_reQuest") 'XmlHttpRequest' >>> camel("xml_HTTP_reQuest", preserve_upper=True) 'XmlHTTPReQuest' Returns: str: CamelCased version of `s`. """ if isinstance(lower_initial, bool): lower_initial = [0] if lower_initial else [] else: lower_initial = listify(lower_initial) upper_segments = listify(upper_segments) result = [] for word in _whitespace_group_re.split(s): segments = [segment for segment in word.split(sep) if segment] count = len(segments) for i, segment in enumerate(segments): upper = i in upper_segments or (i - count) in upper_segments lower = i in lower_initial or (i - count) in lower_initial if upper and lower: if preserve_upper: segment = segment[0] + segment[1:].upper() else: segment = segment[0].lower() + segment[1:].upper() elif upper: segment = segment.upper() elif lower: if not preserve_upper: segment = segment.lower() elif preserve_upper: segment = segment[0].upper() + segment[1:] else: segment = segment[0].upper() + segment[1:].lower() result.append(segment) return "".join(result)
def test_none(self): self.assertEqual([], listify(None)) self.assertEqual([], listify(None, minlen=0)) self.assertEqual([None], listify(None, minlen=1)) self.assertEqual([None, None], listify(None, minlen=2))
def test_minlen(self): assert [] == listify([]) assert [] == listify([], minlen=None) assert [] == listify([], minlen=-1) assert [] == listify([], minlen=0) assert [None] == listify([], minlen=1) assert [None, None] == listify([], minlen=2) assert [None, None, None] == listify([], minlen=3) assert ['a'] == listify(['a']) assert ['a'] == listify(['a'], minlen=None) assert ['a'] == listify(['a'], minlen=-1) assert ['a'] == listify(['a'], minlen=0) assert ['a'] == listify(['a'], minlen=1) assert ['a', None] == listify(['a'], minlen=2) assert ['a', None, None] == listify(['a'], minlen=3) assert ['a', 'b'] == listify(['a', 'b']) assert ['a', 'b'] == listify(['a', 'b'], minlen=None) assert ['a', 'b'] == listify(['a', 'b'], minlen=-1) assert ['a', 'b'] == listify(['a', 'b'], minlen=0) assert ['a', 'b'] == listify(['a', 'b'], minlen=1) assert ['a', 'b'] == listify(['a', 'b'], minlen=2) assert ['a', 'b', None] == listify(['a', 'b'], minlen=3) assert ['a', 'b', None, None] == listify(['a', 'b'], minlen=4)
def resolve(name, modules=None): """ Resolve a dotted name to an object (usually class, module, or function). If `name` is a string, attempt to resolve it according to Python dot notation, e.g. "path.to.MyClass". If `name` is anything other than a string, return it immediately: >>> resolve("calendar.TextCalendar") <class 'calendar.TextCalendar'> >>> resolve(object()) <object object at 0x...> If `modules` is specified, then resolution of `name` is restricted to the given modules. Leading dots are allowed in `name`, but they are ignored. Resolution **will not** traverse up the module path if `modules` is specified. If `modules` is not specified and `name` has leading dots, then resolution is first attempted relative to the calling function's module, and then absolutely. Resolution **will** traverse up the module path. If `name` has no leading dots, resolution is first attempted absolutely and then relative to the calling module. Pass an empty string for `modules` to only use absolute resolution. Warning: Do not resolve strings supplied by an end user without specifying `modules`. Instantiating an arbitrary object specified by an end user can introduce a potential security risk. To avoid this, restrict the search path by explicitly specifying `modules`. Restricting `name` resolution to a set of `modules`: >>> resolve("pockets.camel") <function camel at 0x...> >>> resolve("pockets.camel", modules=["re", "six"]) Traceback (most recent call last): ValueError: Unable to resolve 'pockets.camel' in modules: ['re', 'six'] ... Args: name (str or object): A dotted name. modules (str, module, or list, optional): A module or list of modules, under which to search for `name`. Returns: object: The object specified by `name`. Raises: ValueError: If `name` can't be resolved. """ if not isinstance(name, string_types): return name obj_path = splitify(name, ".", include_empty=True) search_paths = [] if modules is not None: while not obj_path[0].strip(): obj_path.pop(0) for module_path in listify(modules): search_paths.append(splitify(module_path, ".") + obj_path) else: caller = inspect.getouterframes(inspect.currentframe())[1][0].f_globals module_path = caller["__name__"].split(".") if not obj_path[0]: obj_path.pop(0) while not obj_path[0]: obj_path.pop(0) if module_path: module_path.pop() search_paths.append(module_path + obj_path) search_paths.append(obj_path) else: search_paths.append(obj_path) search_paths.append(module_path + obj_path) exceptions = [] for path in search_paths: # Import the most deeply nested module available module = None module_path = [] obj_path = list(path) while obj_path: module_name = obj_path.pop(0) while not module_name: module_name = obj_path.pop(0) if isinstance(module_name, string_types): package = ".".join(module_path + [module_name]) try: module = __import__(package, fromlist=module_name) except ImportError as ex: exceptions.append(ex) obj_path = [module_name] + obj_path break else: module_path.append(module_name) else: module = module_name module_path.append(module.__name__) if module: if obj_path: try: return functools.reduce(getattr, obj_path, module) except AttributeError as ex: exceptions.append(ex) else: return module if modules: msg = "Unable to resolve '{0}' in modules: {1}".format(name, modules) else: msg = "Unable to resolve '{0}'".format(name) if exceptions: msgs = ["{0}: {1}".format(type(e).__name__, e) for e in exceptions] raise ValueError("\n ".join([msg] + msgs)) else: raise ValueError(msg)