Source code for pandasdmx.utils
# pandaSDMX is licensed under the Apache 2.0 license a copy of which
# is included in the source distribution of pandaSDMX.
# This is notwithstanding any licenses of third-party software included in
# this distribution.
# (c) 2014, 2015 Dr. Leo <fhaxbox66qgmail.com>
'''
module pandasdmx.utils - helper classes and functions
'''
from .aadict import aadict
from pandasdmx.utils.anynamedtuple import namedtuple
from itertools import chain
import sys
[docs]class DictLike(aadict):
'''Thin wrapper around dict type
It allows attribute-like item access, has a :meth:`find` method and inherits other
useful features from aadict.
'''
[docs] def aslist(self):
'''
return values() as unordered list
'''
return list(self.values())
[docs] def any(self):
'''
return an arbitrary or the only value. If dict is empty,
raise KeyError.
'''
try:
return next(iter(self.values()))
except StopIteration:
raise KeyError('DictLike is empty.')
[docs] def find(self, search_str, by='name', language='en'):
'''Select values by attribute
Args:
searchstr(str): the string to search for
by(str): the name of the attribute to search by, defaults to 'name'
The specified attribute must be either a string
or a dict mapping language codes to strings.
Such attributes occur, e.g. in :class:`pandasdmx.model.NameableArtefact` which is
a base class for :class:`pandasdmx.model.DataFlowDefinition` and many others.
language(str): language code specifying the language of the text to be searched, defaults to 'en'
Returns:
DictLike: items where value.<by> contains the search_str. International strings
stored as dict with language codes as keys are
searched. Capitalization is ignored.
'''
s = search_str.lower()
# We distinguish between international strings stored as dict such as
# name.en, name.fr, and normal strings.
if by in ['name', 'description']:
get_field = lambda obj: getattr(obj, by)[language]
else: # normal string
get_field = lambda obj: getattr(obj, by)
return DictLike(result for result in self.items()
if s in get_field(result[1]).lower())
[docs]class NamedTupleFactory:
"""
Wrap namedtuple function from the collections stdlib module
to return a singleton if a nametuple with the same field names
has already been created.
"""
cache = {}
def __call__(self, name, fields):
"""
return namedtuple class as singleton
"""
fields = tuple(fields)
if not fields in self.cache:
self.cache[fields] = namedtuple(
name, fields)
return self.cache[fields]
namedtuple_factory = NamedTupleFactory()
[docs]def concat_namedtuples(*tup, **kwargs):
'''
Concatenate 2 or more namedtuples. The new namedtuple type
is provided by :class:`NamedTupleFactory`
return new namedtuple instance
'''
name = kwargs['name'] if 'name' in kwargs else None
# filter out empty elements
filtered = [i for i in filter(None, tup)]
if filtered:
if len(filtered) == 1:
return filtered[0]
else:
fields = chain(*(t._fields for t in filtered))
values = chain(*(t for t in filtered))
if not name:
name = 'SDMXNamedTuple'
ConcatType = namedtuple_factory(name, fields)
return ConcatType(*values)
else:
return ()
# 2to3 compatibility
if sys.version[0] == '3':
str_type = str
else:
str_type = unicode
[docs]def str2bool(s):
if isinstance(s, str_type) and s.lower() == 'false':
return False
return bool(s)
[docs]class LazyDict(dict):
'''
lazily comput values by calling func(k)
'''
def __init__(self, func, *args, **kwargs):
'''
func: callable to return d[k]
'''
super(LazyDict, self).__init__(*args, **kwargs)
self.__func__ = func
self.__computed__ = set()
def __getitem__(self, k):
if k not in self.__computed__:
v = self.__func__(k)
self.__computed__.add(k)
self[k] = v
return super(LazyDict, self).__getitem__(k)