From ba82c57193279a21a6ff396e72f8c9e0e50867ab Mon Sep 17 00:00:00 2001 From: Christof Schulze Date: Mon, 27 Jan 2020 20:56:31 +0100 Subject: [PATCH] added missing functions for CLI and Environment parsing --- collections.py | 54 +++++++++++++++++++++++- parsing/__init__.py | 16 ++++++- singleton.py | 15 +++++++ tests/test_parsing/test_humanize_time.py | 11 +++++ 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 tests/test_parsing/test_humanize_time.py diff --git a/collections.py b/collections.py index 091a1d4..c3b676d 100644 --- a/collections.py +++ b/collections.py @@ -16,7 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Ammsml. If not, see . -from collections.abc import Sequence + +from collections.abc import Sequence, Mapping, Hashable def is_string(seq): @@ -55,11 +56,14 @@ def count(seq): Resembles the `collections.Counter` class functionality, which is deprecated since version 3.3, will be removed in version 3.9 - It is meant to be replaced when the we find something equivalent in maybe collections.abc + It is meant to be replaced when we find something equivalent in maybe collections.abc this code also runs on Python 2.6.* where collections.Counter is not available. + :param seq: + :return: """ + if not is_iterable(seq): raise Exception('Argument provided is not an iterable') @@ -69,3 +73,49 @@ def count(seq): for elem in seq: counters[elem] = counters.get(elem, 0) + 1 return counters + + +class ImmutableDict(Hashable, Mapping): + """ + Dictionary that cannot be updated + """ + def __init__(self, *args, **kwargs): + self._store = dict(*args, **kwargs) + + def __getitem__(self, key): + return self._store[key] + + def __iter__(self): + return self._store.__iter__() + + def __len__(self): + return self._store.__len__() + + def __hash__(self): + return hash(frozenset(self.items())) + + def __repr__(self): + return 'ImmutableDict({0})'.format(repr(self._store)) + + def union(self, overriding_mapping): + """ + Create an ImmutableDict as a combination of the original and overriding_mapping + + :param overriding_mapping: A Mapping of replacement and additional items + :return: A copy of the ImmutableDict with key-value pairs from the overriding_mapping added + + If any of the keys in overriding_mapping are already present in the original ImmutableDict, + the overriding_mapping item replaces the one in the original ImmutableDict. + """ + return ImmutableDict(self._store, **overriding_mapping) + + def difference(self, subtractive_iterable): + """ + Create an ImmutableDict as a combination of the original minus keys in subtractive_iterable + + :param subtractive_iterable: Any iterable containing keys that should not be present in the new ImmutableDict + :return: A copy of the ImmutableDict with keys from the subtractive_iterable removed + """ + remove_keys = frozenset(subtractive_iterable) + keys = (k for k in self._store.keys() if k not in remove_keys) + return ImmutableDict((k, self._store[k]) for k in keys) diff --git a/parsing/__init__.py b/parsing/__init__.py index cdfd95f..1a1e5df 100644 --- a/parsing/__init__.py +++ b/parsing/__init__.py @@ -79,6 +79,20 @@ def is_quoted(data): return len(data) > 1 and data[0] == data[-1] and data[0] in ('"', "'") and data[-2] != '\\' - +def humanize_time(d) -> str: + """ + Return a human-friendly description of elapsed time + :param d: + :return: + """ + human = "" + if d >= 3600: + human += "%dh" % (int(d) // 3600) + d %= 3600 + if d >= 60: + human += "%dm" % (int(d) // 60) + d %= 60 + human += ("%.3fs" % d) + return human diff --git a/singleton.py b/singleton.py index ce9f97f..bb4b69e 100644 --- a/singleton.py +++ b/singleton.py @@ -57,3 +57,18 @@ def with_metaclass(meta, *bases): def __prepare__(cls, name, this_bases): return meta.__prepare__(name, bases) return type.__new__(metaclass, 'temporary_class', (), {}) + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper \ No newline at end of file diff --git a/tests/test_parsing/test_humanize_time.py b/tests/test_parsing/test_humanize_time.py new file mode 100644 index 0000000..4b9cbea --- /dev/null +++ b/tests/test_parsing/test_humanize_time.py @@ -0,0 +1,11 @@ +import unittest +from ammsml.utils.parsing import humanize_time + + +class Test_humanize_time(unittest.TestCase): + def test_something(self): + self.assertEqual(True, False) + # TODO + +if __name__ == '__main__': + unittest.main() -- GitLab