diff --git a/misc/IDE files/Wing/python_toolbox_py3_wing6.wpr b/misc/IDE files/Wing/python_toolbox_py3_wing6.wpr new file mode 100644 index 000000000..0a104a906 --- /dev/null +++ b/misc/IDE files/Wing/python_toolbox_py3_wing6.wpr @@ -0,0 +1,53 @@ +#!wing +#!version=6.0 +################################################################## +# Wing IDE project file # +################################################################## +[project attributes] +debug.launch-configs = (1, + {'launch-OHU716PSo2P5T54y': ({}, + {'buildcmd': ('default', + None), + 'env': ('project', + [u'']), + 'name': u'Launch Config 1', + 'pyexec': ('default', + u''), + 'pypath': ('default', + ''), + 'pyrunargs': ('project', + u''), + 'runargs': u'', + 'rundir': ('default', + u'')})}) +proj.directory-list = [{'dirloc': loc('../../..'), + 'excludes': [u'nosetests.xml', + u'source_py2', + u'.coverage_html_report', + u'build', + u'source_py3/.coverage_html_report', + u'source_py3/python_toolbox.egg-info', + u'dist', + u'docs/_build', + u'python_toolbox.egg-info'], + 'filter': '*', + 'include_hidden': False, + 'recursive': True, + 'watch_for_changes': True}] +proj.file-type = 'shared' +proj.home-dir = loc('../../..') +proj.shared-attribute-names = ['proj.shared-attribute-names', + 'proj.directory-list', + 'proj.file-list', + 'proj.file-type', + 'proj.main-file', + 'proj.home-dir', + 'testing.auto-test-file-specs', + 'testing.test-file-list', + 'testing.test-framework', + 'debug.named-entry-points', + 'debug.launch-configs', + 'console.toolbox'] +testing.auto-test-file-specs = [('regex', + 'test_python_toolbox(/test[^/.]*)+[.]py')] +testing.test-framework = {None: 'nose'} diff --git a/setup.cfg b/setup.cfg index ffc21c7ee..46d5e331c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [nosetests] where=source_py2/test_python_toolbox py3where=source_py3/test_python_toolbox - + verbosity=3 detailed-errors=1 diff --git a/source_py3/python_toolbox/abc_tools.py b/source_py3/python_toolbox/abc_tools.py index 5affb74ae..805e8523e 100644 --- a/source_py3/python_toolbox/abc_tools.py +++ b/source_py3/python_toolbox/abc_tools.py @@ -3,20 +3,9 @@ '''Defines tools related to abstract base classes from the `abc` module.''' +import abc -class AbstractStaticMethod(staticmethod): - ''' - A combination of `abc.abstractmethod` and `staticmethod`. - - A method which (a) doesn't take a `self` argument and (b) must be - overridden in any subclass if you want that subclass to be instanciable. - - This class is good only for documentation; it doesn't enforce overriding - methods to be static. - ''' - __slots__ = () - __isabstractmethod__ = True - - def __init__(self, function): - super().__init__(function) - function.__isabstractmethod__ = True + +def abstract_whatever(_=None): + return abc.abstractmethod(lambda: None) + \ No newline at end of file diff --git a/source_py3/python_toolbox/caching/__init__.py b/source_py3/python_toolbox/caching/__init__.py index 8b69b4df9..75972ceaa 100644 --- a/source_py3/python_toolbox/caching/__init__.py +++ b/source_py3/python_toolbox/caching/__init__.py @@ -6,5 +6,5 @@ # todo: examine thread-safety from .decorators import cache -from .cached_type import CachedType +from .cached_type import CachedType, StrongCachedType from .cached_property import CachedProperty \ No newline at end of file diff --git a/source_py3/python_toolbox/caching/cached_type.py b/source_py3/python_toolbox/caching/cached_type.py index be2f75a52..fdc4da23c 100644 --- a/source_py3/python_toolbox/caching/cached_type.py +++ b/source_py3/python_toolbox/caching/cached_type.py @@ -7,14 +7,65 @@ See its documentation for more details. ''' -from python_toolbox.sleek_reffing import SleekCallArgs +import threading +import contextlib +from python_toolbox.function_tools import CallArgs, SleekCallArgs + +from .cached_property import CachedProperty + + +class InConstructionMarker: + def __init__(self): + self.lock = threading.Lock() + class SelfPlaceholder: '''Placeholder for `self` when storing call-args.''' -class CachedType(type): +class BaseCachedType(type): + _BaseCachedType__call_args_type = None + + def __new__(mcls, *args, **kwargs): + cls = super().__new__(mcls, *args, **kwargs) + cls.__cache = {} + cls.__quick_lock = threading.Lock() + cls.__construction_lock = threading.Lock() + return cls + + + def __call__(cls, *args, **kwargs): + call_args = cls._BaseCachedType__call_args_type( + cls.__cache, + cls.__init__, + *((SelfPlaceholder,) + args), + **kwargs + ) + + while True: + cls.__quick_lock.acquire() + try: + cached_value = cls.__cache[call_args] + except KeyError: + cls.__cache[call_args] = in_construction_marker = \ + InConstructionMarker() + cls.__quick_lock.release() + with in_construction_marker.lock: + cls.__cache[call_args] = new_instance = \ + super().__call__(*args, **kwargs) + return new_instance + else: + cls.__quick_lock.release() + if isinstance(cached_value, InConstructionMarker): + in_construction_marker = cached_value + with in_construction_marker.lock: + continue + else: + return cached_value + + +class CachedType(BaseCachedType): ''' A metaclass for sharing instances. @@ -39,24 +90,31 @@ def __init__(self, a, b=2): you can avoid memory leaks when using weakreffable arguments, but if you ever want to use non-weakreffable arguments you are still able to. (Assuming you don't mind the memory leaks.) + + If you want a non-weakreffing version, use `StrongCachedType` instead. ''' + _BaseCachedType__call_args_type = SleekCallArgs - def __new__(mcls, *args, **kwargs): - result = super().__new__(mcls, *args, **kwargs) - result.__cache = {} - return result - - def __call__(cls, *args, **kwargs): - sleek_call_args = SleekCallArgs( - cls.__cache, - cls.__init__, - *((SelfPlaceholder,) + args), - **kwargs - ) - try: - return cls.__cache[sleek_call_args] - except KeyError: - cls.__cache[sleek_call_args] = value = \ - super().__call__(*args, **kwargs) - return value +class StrongCachedType(BaseCachedType): + ''' + A metaclass for sharing instances. + + For example, if you have a class like this: + + class Grokker(object, metaclass=caching.CachedType): + def __init__(self, a, b=2): + self.a = a + self.b = b + + Then all the following calls would result in just one instance: + + Grokker(1) is Grokker(1, 2) is Grokker(b=2, a=1) is Grokker(1, **{}) + + This metaclass understands keyword arguments. + + The arguments will not be weakreffed. If you want a weakreffing version, + use `CachedType` instead. + ''' + _BaseCachedType__call_args_type = CallArgs + \ No newline at end of file diff --git a/source_py3/python_toolbox/caching/decorators.py b/source_py3/python_toolbox/caching/decorators.py index af04d9787..aa2784037 100644 --- a/source_py3/python_toolbox/caching/decorators.py +++ b/source_py3/python_toolbox/caching/decorators.py @@ -13,7 +13,6 @@ from python_toolbox import misc_tools from python_toolbox import binary_search from python_toolbox import decorator_tools -from python_toolbox.sleek_reffing import SleekCallArgs infinity = float('inf') @@ -32,8 +31,9 @@ def _get_now(): @decorator_tools.helpful_decorator_builder -def cache(max_size=infinity, time_to_keep=None): +def cache(*, max_size=infinity, time_to_keep=None, weakref_when_possible=True): ''' + blocktododoc Cache a function, saving results so they won't have to be computed again. This decorator understands function arguments. For example, it understands @@ -46,14 +46,17 @@ def f(a, b=2): The calls `f(1)` or `f(1, 2)` or `f(b=2, a=1)` are all identical, and a cached result saved for one of these calls will be used for the others. - All the arguments are sleekreffed to prevent memory leaks. Sleekref is a - variation of weakref. Sleekref is when you try to weakref an object, but if - it's non-weakreffable, like a `list` or a `dict`, you maintain a normal, - strong reference to it. (See documentation of - `python_toolbox.sleek_reffing` for more details.) Thanks to sleekreffing - you can avoid memory leaks when using weakreffable arguments, but if you - ever want to use non-weakreffable arguments you are still able to. - (Assuming you don't mind the memory leaks.) + If `weakref_when_possible=True` (default) all the arguments are sleekreffed + to prevent memory leaks. Sleekref is a variation of weakref. Sleekref is + when you try to weakref an object, but if it's non-weakreffable, like a + `list` or a `dict`, you maintain a normal, strong reference to it. (See + documentation of `python_toolbox.sleek_reffing` for more details.) Thanks + to sleekreffing you can avoid memory leaks when using weakreffable + arguments, but if you ever want to use non-weakreffable arguments you are + still able to. (Assuming you don't mind the memory leaks.) + + If you want to use a non-weakreffing version, pass in + `weakref_when_possible=False`. You may optionally specify a `max_size` for maximum number of cached results to store; old entries are thrown away according to a @@ -69,6 +72,9 @@ def f(a, b=2): # completely argumentless function. so do one for those. from python_toolbox.nifty_collections import OrderedDict + from python_toolbox.function_tools import CallArgs, SleekCallArgs + + call_args_type = SleekCallArgs if weakref_when_possible else CallArgs if time_to_keep is not None: if max_size != infinity: @@ -94,8 +100,8 @@ def decorator(function): if time_to_keep: - sorting_key_function = lambda sleek_call_args: \ - cached._cache[sleek_call_args][1] + sorting_key_function = (lambda call_args: + cached._cache[call_args][1]) def remove_expired_entries(): @@ -114,13 +120,13 @@ def remove_expired_entries(): @misc_tools.set_attributes(_cache=OrderedDict()) def cached(function, *args, **kwargs): remove_expired_entries() - sleek_call_args = \ - SleekCallArgs(cached._cache, function, *args, **kwargs) + call_args = call_args_type(cached._cache, function, *args, + **kwargs) try: - return cached._cache[sleek_call_args][0] + return cached._cache[call_args][0] except KeyError: value = function(*args, **kwargs) - cached._cache[sleek_call_args] = ( + cached._cache[call_args] = ( value, _get_now() + time_to_keep ) @@ -131,28 +137,28 @@ def cached(function, *args, **kwargs): @misc_tools.set_attributes(_cache={}) def cached(function, *args, **kwargs): - sleek_call_args = \ - SleekCallArgs(cached._cache, function, *args, **kwargs) + call_args = call_args_type(cached._cache, function, *args, + **kwargs) try: - return cached._cache[sleek_call_args] + return cached._cache[call_args] except KeyError: - cached._cache[sleek_call_args] = value = \ - function(*args, **kwargs) + cached._cache[call_args] = value = \ + function(*args, **kwargs) return value else: # max_size < infinity @misc_tools.set_attributes(_cache=OrderedDict()) def cached(function, *args, **kwargs): - sleek_call_args = \ - SleekCallArgs(cached._cache, function, *args, **kwargs) + call_args = call_args_type(cached._cache, function, *args, + **kwargs) try: - result = cached._cache[sleek_call_args] - cached._cache.move_to_end(sleek_call_args) + result = cached._cache[call_args] + cached._cache.move_to_end(call_args) return result except KeyError: - cached._cache[sleek_call_args] = value = \ - function(*args, **kwargs) + cached._cache[call_args] = value = \ + function(*args, **kwargs) if len(cached._cache) > max_size: cached._cache.popitem(last=False) return value diff --git a/source_py3/python_toolbox/combi/chain_space.py b/source_py3/python_toolbox/combi/chain_space.py index 24b5bf0c8..30ed6064b 100644 --- a/source_py3/python_toolbox/combi/chain_space.py +++ b/source_py3/python_toolbox/combi/chain_space.py @@ -14,7 +14,7 @@ -class ChainSpace(sequence_tools.CuteSequenceMixin, collections.Sequence): +class ChainSpace(sequence_tools.CuteSequenceMixin, collections.abc.Sequence): ''' A space of sequences chained together. diff --git a/source_py3/python_toolbox/combi/map_space.py b/source_py3/python_toolbox/combi/map_space.py index 2ee42527c..14450f073 100644 --- a/source_py3/python_toolbox/combi/map_space.py +++ b/source_py3/python_toolbox/combi/map_space.py @@ -11,7 +11,7 @@ -class MapSpace(sequence_tools.CuteSequenceMixin, collections.Sequence): +class MapSpace(sequence_tools.CuteSequenceMixin, collections.abc.Sequence): ''' A space of a function applied to a sequence. diff --git a/source_py3/python_toolbox/combi/perming/perm.py b/source_py3/python_toolbox/combi/perming/perm.py index fe71b9fab..70bc566b2 100644 --- a/source_py3/python_toolbox/combi/perming/perm.py +++ b/source_py3/python_toolbox/combi/perming/perm.py @@ -31,7 +31,7 @@ def __getitem__(self, i): pass class PermItems(sequence_tools.CuteSequenceMixin, _BasePermView, - collections.Sequence): + collections.abc.Sequence): ''' A viewer of a perm's items, similar to `dict.items()`. @@ -46,7 +46,7 @@ def __getitem__(self, i): class PermAsDictoid(sequence_tools.CuteSequenceMixin, _BasePermView, - collections.Mapping): + collections.abc.Mapping): '''A dict-like interface to a `Perm`.''' def __getitem__(self, key): return self.perm[key] @@ -69,7 +69,7 @@ def __call__(cls, item, perm_space=None): @functools.total_ordering -class Perm(sequence_tools.CuteSequenceMixin, collections.Sequence, +class Perm(sequence_tools.CuteSequenceMixin, collections.abc.Sequence, metaclass=PermType): ''' A permutation of items from a `PermSpace`. @@ -107,7 +107,7 @@ def __init__(self, perm_sequence, perm_space=None): ''' perm_space = None if perm_space is None \ else PermSpace.coerce(perm_space) - assert isinstance(perm_sequence, collections.Iterable) + assert isinstance(perm_sequence, collections.abc.Iterable) perm_sequence = sequence_tools. \ ensure_iterable_is_immutable_sequence(perm_sequence) diff --git a/source_py3/python_toolbox/combi/perming/perm_space.py b/source_py3/python_toolbox/combi/perming/perm_space.py index 82e86eeb6..4e4891817 100644 --- a/source_py3/python_toolbox/combi/perming/perm_space.py +++ b/source_py3/python_toolbox/combi/perming/perm_space.py @@ -59,7 +59,7 @@ def __call__(cls, *args, **kwargs): class PermSpace(_VariationRemovingMixin, _VariationAddingMixin, _FixedMapManagingMixin, sequence_tools.CuteSequenceMixin, - collections.Sequence, metaclass=PermSpaceType): + collections.abc.Sequence, metaclass=PermSpaceType): ''' A space of permutations on a sequence. @@ -165,7 +165,7 @@ def __init__(self, iterable_or_length, n_elements=None, *, domain=None, # # assert isinstance( iterable_or_length, - (collections.Iterable, numbers.Integral) + (collections.abc.Iterable, numbers.Integral) ) if isinstance(iterable_or_length, numbers.Integral): assert iterable_or_length >= 0 @@ -186,7 +186,7 @@ def __init__(self, iterable_or_length, n_elements=None, *, domain=None, self.sequence = sequence_tools.CuteRange(iterable_or_length) self.sequence_length = iterable_or_length else: - assert isinstance(iterable_or_length, collections.Iterable) + assert isinstance(iterable_or_length, collections.abc.Iterable) self.sequence = sequence_tools. \ ensure_iterable_is_immutable_sequence(iterable_or_length) range_candidate = sequence_tools.CuteRange(len(self.sequence)) @@ -268,7 +268,7 @@ def __init__(self, iterable_or_length, n_elements=None, *, domain=None, if fixed_map is None: fixed_map = {} if not isinstance(fixed_map, dict): - if isinstance(fixed_map, collections.Callable): + if isinstance(fixed_map, collections.abc.Callable): fixed_map = {item: fixed_map(item) for item in self.sequence} else: fixed_map = dict(fixed_map) @@ -754,7 +754,7 @@ def __getitem__(self, i): def index(self, perm): '''Get the index number of permutation `perm` in this space.''' - if not isinstance(perm, collections.Iterable): + if not isinstance(perm, collections.abc.Iterable): raise ValueError try: diff --git a/source_py3/python_toolbox/combi/product_space.py b/source_py3/python_toolbox/combi/product_space.py index a88e88dd7..9da04d935 100644 --- a/source_py3/python_toolbox/combi/product_space.py +++ b/source_py3/python_toolbox/combi/product_space.py @@ -7,7 +7,7 @@ from python_toolbox import sequence_tools -class ProductSpace(sequence_tools.CuteSequenceMixin, collections.Sequence): +class ProductSpace(sequence_tools.CuteSequenceMixin, collections.abc.Sequence): ''' A product space between sequences. @@ -70,7 +70,7 @@ def __getitem__(self, i): def index(self, given_sequence): '''Get the index number of `given_sequence` in this product space.''' - if not isinstance(given_sequence, collections.Sequence) or \ + if not isinstance(given_sequence, collections.abc.Sequence) or \ not len(given_sequence) == len(self.sequences): raise ValueError diff --git a/source_py3/python_toolbox/combi/selection_space.py b/source_py3/python_toolbox/combi/selection_space.py index a5d7f5700..9feb5fa63 100644 --- a/source_py3/python_toolbox/combi/selection_space.py +++ b/source_py3/python_toolbox/combi/selection_space.py @@ -7,7 +7,7 @@ class SelectionSpace(sequence_tools.CuteSequenceMixin, - collections.Sequence): + collections.abc.Sequence): ''' Space of possible selections of any number of items from `sequence`. @@ -74,7 +74,7 @@ def __getitem__(self, i): def index(self, selection): '''Find the index number of `selection` in this `SelectionSpace`.''' - if not isinstance(selection, collections.Iterable): + if not isinstance(selection, collections.abc.Iterable): raise ValueError selection_set = set(selection) diff --git a/source_py3/python_toolbox/context_management/__init__.py b/source_py3/python_toolbox/context_management/__init__.py index 5a0f4aa82..9cdee899d 100644 --- a/source_py3/python_toolbox/context_management/__init__.py +++ b/source_py3/python_toolbox/context_management/__init__.py @@ -122,7 +122,6 @@ def do_stuff(): # todo: for case of decorated generator, possibly make getstate (or whatever) # that will cause it to be pickled by reference to the decorated function - from .abstract_context_manager import AbstractContextManager from .context_manager_type_type import ContextManagerTypeType from .context_manager_type import ContextManagerType diff --git a/source_py3/python_toolbox/context_management/context_manager.py b/source_py3/python_toolbox/context_management/context_manager.py index 06964a77e..600736d24 100644 --- a/source_py3/python_toolbox/context_management/context_manager.py +++ b/source_py3/python_toolbox/context_management/context_manager.py @@ -70,12 +70,12 @@ def __enter_using_manage_context(self): try: generator_return_value = next(new_generator) - return self if (generator_return_value is SelfHook) else \ - generator_return_value - except StopIteration: raise RuntimeError("The generator didn't yield even one time; it " "must yield one time exactly.") + else: + return self if (generator_return_value is SelfHook) else \ + generator_return_value def __exit_using_manage_context(self, exc_type, exc_value, exc_traceback): @@ -97,7 +97,8 @@ def __exit_using_manage_context(self, exc_type, exc_value, exc_traceback): raise RuntimeError( "The generator didn't stop after the yield; possibly you " "have more than one `yield` in the generator function? " - "The generator function must `yield` exactly one time.") + "The generator function must `yield` exactly one time." + ) else: if exc_value is None: # Need to force instantiation so we can reliably diff --git a/source_py3/python_toolbox/context_management/thread_pool_exit_stack.py b/source_py3/python_toolbox/context_management/thread_pool_exit_stack.py new file mode 100644 index 000000000..aebb0edd2 --- /dev/null +++ b/source_py3/python_toolbox/context_management/thread_pool_exit_stack.py @@ -0,0 +1,207 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import contextlib +import threading + +from python_toolbox.nifty_collections import CuteEnum +from python_toolbox import decorator_tools + +from .abstract_context_manager import AbstractContextManager +from .context_manager import ContextManager + + +def _get_enterer(enterable): + enter_function = type(exitable).__enter__ + return (lambda: enter_function(enterable)) + + +def _get_exiter(exitable): + exit_function = type(exitable).__exit__ + return ( + lambda exc_type, exc_value, exc_traceback: + exit_function(exitable, exc_type, exc_value, exc_traceback) + ) + + + +class StackState(CuteEnum): + CREATED = 0 + ENTERED = 1 + EXITING = 2 + EXITED = 3 + + + +@decorator_tools.decorator +def _with_lock(function, *args, **kwargs): + thread_pool_exit_stack = args[0] + assert isinstance(thread_pool_exit_stack, ThreadPoolExitStack) + with thread_pool_exit_stack._lock: + return function(*args, **kwargs) + + +class ThreadPoolExitStack(ContextManager): + """ + blocktododoc + Context manager for dynamic management of a stack of exit callbacks + + For example: + + with ExitStack() as stack: + files = [stack.enter_context(open(fname)) for fname in filenames] + # All opened files will automatically be closed at the end of + # the with statement, even if attempts to open files later + # in the list raise an exception + + """ + def __init__(self, *, executor=None, n_threads=None): + from python_toolbox.future_tools import CuteThreadPoolExecutor + self._exit_callbacks = deque() + self._lock = threading.RLock() + self._state = StackState.CREATED + if executor is None: + self._executor = \ + CuteThreadPoolExecutor(10 if n_threads is None else n_threads) + self.enter_context(self._executor) + else: + assert n_threads is None + self._executor = executor + + @_with_lock + def pop_all(self): + """Preserve the context stack by transferring it to a new instance""" + new_stack = type(self)() + new_stack._exit_callbacks = self._exit_callbacks + self._exit_callbacks = deque() + return new_stack + + @_with_lock + def push(self, exit): + """Registers a callback with the standard __exit__ method signature + + Can suppress exceptions the same way __exit__ methods can. + + Also accepts any object with an __exit__ method (registering a call + to the method instead of the object itself) + """ + # We use an unbound method rather than a bound method to follow + # the standard lookup behaviour for special methods + _cb_type = type(exit) + try: + exit_method = _cb_type.__exit__ + except AttributeError: + # Not a context manager, so assume its a callable + self._exit_callbacks.append(exit) + else: + self._push_context_manager_exit(exit, exit_method) + return exit # Allow use as a decorator + + @_with_lock + def callback(self, callback, *args, **kwds): + """Registers an arbitrary callback and arguments. + + Cannot suppress exceptions. + """ + def _exit_wrapper(exc_type, exc, tb): + callback(*args, **kwds) + # We changed the signature, so using @wraps is not appropriate, but + # setting __wrapped__ may still help with introspection + _exit_wrapper.__wrapped__ = callback + self.push(_exit_wrapper) + return callback # Allow use as a decorator + + def enter_context(self, context_manager): + (result,) = self.enter_contexts((context_manager,)) + return result + + + def __enter_in_thread(self, context_manager): + enterer = _get_enterer(context_manager) + enter_value = enterer() + self.push(_get_exiter(context_manager)) + return enter_value + + @_with_lock + def enter_contexts(self, context_managers): + """Enters the supplied context manager + + If successful, also pushes its __exit__ method as a callback and + returns the result of the __enter__ method. + """ + from python_toolbox import sequence_tools + # We look up the special methods on the type to match the with statement + if self._state != StackState.ENTERED: + raise RuntimeError + + return tuple( + self._executor.map(self.enter_in_thread, context_managers) + ) + + def close(self): + """Immediately unwind the context stack""" + self.__exit__(None, None, None) + + @_with_lock + def __enter__(self): + if self._state != StackState.CREATED: + raise RuntimeError + return self + + @_with_lock + def __exit__(self, *exc_details): + # blocktodo: add threading here too, to exit concurrently? + if self._state != StackState.ENTERED: + raise RuntimeError + self._state = StackState.EXITING + + received_exc = exc_details[0] is not None + + # We manipulate the exception state so it behaves as though + # we were actually nesting multiple with statements + frame_exc = sys.exc_info()[1] + def _fix_exception_context(new_exc, old_exc): + # Context may not be correct, so find the end of the chain + while 1: + exc_context = new_exc.__context__ + if exc_context is old_exc: + # Context is already set correctly (see issue 20317) + return + if exc_context is None or exc_context is frame_exc: + break + new_exc = exc_context + # Change the end of the chain to point to the exception + # we expect it to reference + new_exc.__context__ = old_exc + + # Callbacks are invoked in LIFO order to match the behaviour of + # nested context managers + suppressed_exc = False + pending_raise = False + while self._exit_callbacks: + cb = self._exit_callbacks.pop() + try: + if cb(*exc_details): + suppressed_exc = True + pending_raise = False + exc_details = (None, None, None) + except: + new_exc_details = sys.exc_info() + # simulate the stack of exceptions by setting the context + _fix_exception_context(new_exc_details[1], exc_details[1]) + pending_raise = True + exc_details = new_exc_details + if pending_raise: + try: + # bare "raise exc_details[1]" replaces our carefully + # set-up context + fixed_ctx = exc_details[1].__context__ + raise exc_details[1] + except BaseException: + exc_details[1].__context__ = fixed_ctx + raise + + assert self._state == StackState.EXITING + self._state = StackState.EXITED + + return received_exc and suppressed_exc diff --git a/source_py3/python_toolbox/cute_testing.py b/source_py3/python_toolbox/cute_testing.py index a84ed4c3f..a27a71811 100644 --- a/source_py3/python_toolbox/cute_testing.py +++ b/source_py3/python_toolbox/cute_testing.py @@ -3,7 +3,6 @@ '''This module defines tools for testing.''' -import nose import sys from python_toolbox.third_party import unittest2 @@ -33,7 +32,7 @@ class RaiseAssertor(context_management.ContextManager): ''' - def __init__(self, exception_type=Exception, text='', + def __init__(self, exception_type=Exception, text='', *, assert_exact_type=False): ''' Construct the `RaiseAssertor`. diff --git a/source_py3/python_toolbox/dict_tools.py b/source_py3/python_toolbox/dict_tools.py index b2201e4dc..8089f5583 100644 --- a/source_py3/python_toolbox/dict_tools.py +++ b/source_py3/python_toolbox/dict_tools.py @@ -118,17 +118,17 @@ def remove_keys(d, keys_to_remove): If key doesn't exist, doesn't raise an exception. ''' - if isinstance(keys_to_remove, collections.Iterable): + if isinstance(keys_to_remove, collections.abc.Iterable): for key in keys_to_remove: try: del d[key] except KeyError: pass else: - if isinstance(keys_to_remove, collections.Container): + if isinstance(keys_to_remove, collections.abc.Container): filter_function = lambda value: value in keys_to_remove else: - assert isinstance(keys_to_remove, collections.Callable) + assert isinstance(keys_to_remove, collections.abc.Callable) filter_function = keys_to_remove for key in list(d.keys()): if filter_function(key): diff --git a/source_py3/python_toolbox/emitting/emitter.py b/source_py3/python_toolbox/emitting/emitter.py index 4957a02b4..689b1c391 100644 --- a/source_py3/python_toolbox/emitting/emitter.py +++ b/source_py3/python_toolbox/emitting/emitter.py @@ -72,7 +72,7 @@ def __init__(self, inputs=(), outputs=(), name=None): inputs = sequence_tools.to_tuple(inputs, item_type=Emitter) outputs = sequence_tools.to_tuple(outputs, - item_type=(collections.Callable, + item_type=(collections.abc.Callable, Emitter)) self._inputs = set() @@ -220,7 +220,7 @@ def add_output(self, thing): If adding an emitter, every time this emitter will emit the output emitter will emit as well. ''' - assert isinstance(thing, (Emitter, collections.Callable)) + assert isinstance(thing, (Emitter, collections.abc.Callable)) self._outputs.add(thing) if isinstance(thing, Emitter): thing._inputs.add(self) @@ -228,7 +228,7 @@ def add_output(self, thing): def remove_output(self, thing): '''Remove an output from this emitter.''' - assert isinstance(thing, (Emitter, collections.Callable)) + assert isinstance(thing, (Emitter, collections.abc.Callable)) self._outputs.remove(thing) if isinstance(thing, Emitter): thing._inputs.remove(self) diff --git a/source_py3/python_toolbox/function_tools/__init__.py b/source_py3/python_toolbox/function_tools/__init__.py new file mode 100644 index 000000000..bba57046c --- /dev/null +++ b/source_py3/python_toolbox/function_tools/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +from .misc import get_default_args_dict +from .call_args import SleekCallArgs, CallArgs \ No newline at end of file diff --git a/source_py3/python_toolbox/sleek_reffing/sleek_call_args.py b/source_py3/python_toolbox/function_tools/call_args.py similarity index 54% rename from source_py3/python_toolbox/sleek_reffing/sleek_call_args.py rename to source_py3/python_toolbox/function_tools/call_args.py index 11a55f975..c3ff6d1e8 100644 --- a/source_py3/python_toolbox/sleek_reffing/sleek_call_args.py +++ b/source_py3/python_toolbox/function_tools/call_args.py @@ -1,24 +1,16 @@ # Copyright 2009-2017 Ram Rachum. # This program is distributed under the MIT license. -''' -Defines the `SleekCallArgs` class. - -See its documentation for more details. -''' +import abc from python_toolbox import cute_inspect from python_toolbox import cheat_hashing - -from .sleek_ref import SleekRef -from .cute_sleek_value_dict import CuteSleekValueDict +from python_toolbox.sleek_reffing import SleekRef, CuteSleekValueDict -__all__ = ['SleekCallArgs'] - - -class SleekCallArgs: +class BaseCallArgs(metaclass=abc.ABCMeta): ''' + blocktododoc A bunch of call args with a sleekref to them. "Call args" is a mapping of which function arguments get which values. @@ -32,13 +24,18 @@ def f(a, b=2): All the argument values are sleekreffed to avoid memory leaks. (See documentation of `python_toolbox.sleek_reffing.SleekRef` for more details.) ''' + + make_ref = None + make_dict = None + hash_function = None + # What if we one of the args gets gc'ed before this SCA gets added to the # dictionary? It will render this SCA invalid, but we'll still be in the # dict. So make note to user: Always keep reference to args and kwargs # until the SCA gets added to the dict. def __init__(self, containing_dict, function, *args, **kwargs): ''' - Construct the `SleekCallArgs`. + Construct the `BaseCallArgs`. `containing_dict` is the `dict` we'll try to remove ourselves from when one of our sleekrefs dies. `function` is the function for which we @@ -51,56 +48,85 @@ def __init__(self, containing_dict, function, *args, **kwargs): ''' args_spec = cute_inspect.getargspec(function) - star_args_name, star_kwargs_name = \ - args_spec.varargs, args_spec.keywords + star_args_name, star_kwargs_name = (args_spec.varargs, + args_spec.keywords) call_args = cute_inspect.getcallargs(function, *args, **kwargs) del args, kwargs - self.star_args_refs = [] - '''Sleekrefs to star-args.''' + self.star_args_refs = () - if star_args_name: - star_args = call_args.pop(star_args_name, None) - if star_args: - self.star_args_refs = [SleekRef(star_arg, self.destroy) for - star_arg in star_args] + star_args = call_args.pop(star_args_name, ()) if star_args_name else () + self.star_args_refs = tuple(self.make_ref(star_arg) for + star_arg in star_args) - self.star_kwargs_refs = {} - '''Sleerefs to star-kwargs.''' - if star_kwargs_name: - star_kwargs = call_args.pop(star_kwargs_name, {}) - if star_kwargs: - self.star_kwargs_refs = CuteSleekValueDict(self.destroy, - star_kwargs) + star_kwargs = (call_args.pop(star_kwargs_name, {}) if + star_kwargs_name else {}) + self.star_kwargs_refs = self.make_dict(star_kwargs) - self.args_refs = CuteSleekValueDict(self.destroy, call_args) - '''Mapping from argument name to value, sleek-style.''' + self.args_refs = self.make_dict(call_args) + '''Mapping from argument name to value.''' # In the future the `.args`, `.star_args` and `.star_kwargs` attributes # may change, so we must record the hash now: - self._hash = cheat_hashing.cheat_hash( + self._hash = self.hash_function( ( + type(self), self.args, self.star_args, self.star_kwargs ) ) + args = abc.abstractproperty() + '''The arguments.''' + + star_args = abc.abstractproperty() + '''Extraneous arguments. (i.e. `*args`.)''' + + star_kwargs = abc.abstractproperty() + '''Extraneous keyword arguments. (i.e. `*kwargs`.)''' + + + def __hash__(self): + return self._hash + + + def __eq__(self, other): + return ( + type(self) == type(other) and + self.args == other.args and + self.star_args == other.star_args and + self.star_kwargs == other.star_kwargs + ) + + + def __ne__(self, other): + return not self == other + + +class CallArgs(BaseCallArgs): + make_ref = lambda self, thing: thing + hash_function = lambda self, thing: hash(thing) + def make_dict(self, items): + from python_toolbox import nifty_collections + return nifty_collections.FrozenDict(items) - args = property(lambda self: dict(self.args_refs)) + + args = property(lambda self: self.args_refs) '''The arguments.''' - star_args = property( - lambda self: - tuple((star_arg_ref() for star_arg_ref in self.star_args_refs)) - ) + star_args = property(lambda self: self.star_args_refs) '''Extraneous arguments. (i.e. `*args`.)''' - star_kwargs = property(lambda self: dict(self.star_kwargs_refs)) + star_kwargs = property(lambda self: self.star_kwargs_refs) '''Extraneous keyword arguments. (i.e. `*kwargs`.)''' - + +class SleekCallArgs(BaseCallArgs): + make_ref = lambda self, thing: SleekRef(thing, self.destroy) + make_dict = lambda self, items: CuteSleekValueDict(self.destroy, items) + hash_function = lambda self, thing: cheat_hashing.cheat_hash(thing) def destroy(self, _=None): '''Delete ourselves from our containing `dict`.''' @@ -110,21 +136,15 @@ def destroy(self, _=None): except KeyError: pass + args = property(lambda self: dict(self.args_refs)) + '''The arguments.''' - def __hash__(self): - return self._hash - - - def __eq__(self, other): - if not isinstance(other, SleekCallArgs): - return NotImplemented - return self.args == other.args and \ - self.star_args == other.star_args and \ - self.star_kwargs == other.star_kwargs - - - def __ne__(self, other): - return not self == other - + star_args = property( + lambda self: + tuple((star_arg_ref() for star_arg_ref in self.star_args_refs)) + ) + '''Extraneous arguments. (i.e. `*args`.)''' - \ No newline at end of file + star_kwargs = property(lambda self: dict(self.star_kwargs_refs)) + '''Extraneous keyword arguments. (i.e. `*kwargs`.)''' + \ No newline at end of file diff --git a/source_py3/python_toolbox/introspection_tools.py b/source_py3/python_toolbox/function_tools/misc.py similarity index 88% rename from source_py3/python_toolbox/introspection_tools.py rename to source_py3/python_toolbox/function_tools/misc.py index b04456035..fb24fab4f 100644 --- a/source_py3/python_toolbox/introspection_tools.py +++ b/source_py3/python_toolbox/function_tools/misc.py @@ -1,12 +1,8 @@ # Copyright 2009-2017 Ram Rachum. # This program is distributed under the MIT license. -'''Defines various introspection tools, similar to the stdlib's `inspect`.''' - from python_toolbox import cute_inspect -from python_toolbox.nifty_collections import OrderedDict - def get_default_args_dict(function): ''' @@ -19,6 +15,8 @@ def get_default_args_dict(function): OrderedDict([('c', 1), ('d', 'meow')]) ''' + from python_toolbox.nifty_collections import OrderedDict + arg_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = arg_spec diff --git a/source_py3/python_toolbox/future_tools.py b/source_py3/python_toolbox/future_tools.py index bbca84c49..faabbae0b 100644 --- a/source_py3/python_toolbox/future_tools.py +++ b/source_py3/python_toolbox/future_tools.py @@ -126,3 +126,25 @@ class CuteProcessPoolExecutor(concurrent.futures.ProcessPoolExecutor, computed, and not the order in which they were submitted. ''' + +class CuteDummyExecutor(BaseCuteExecutor): + ''' + A dummy executor that executes commands synchronously. + + This is a degenerate executor that simply executes all commands that are + fed to it synchronously and immediately, in the same thread as the one it + was called from, as if you didn't use an executor at all. + ''' + + def submit(self, fn, *args, **kwargs): + future = concurrent.futures.Future() + + try: + result = fn(*args, **kwargs) + except BaseException as exception: + future.set_exception(exception) + else: + future.set_result(result) + + return future + \ No newline at end of file diff --git a/source_py3/python_toolbox/logic_tools.py b/source_py3/python_toolbox/logic_tools.py index bd8556fdd..c4585bed9 100644 --- a/source_py3/python_toolbox/logic_tools.py +++ b/source_py3/python_toolbox/logic_tools.py @@ -60,8 +60,8 @@ def all_equivalent(iterable, relation=operator.eq, *, assume_reflexive=True, return all(itertools.starmap(relation, pairs)) -def get_equivalence_classes(iterable, key=None, container=set, *, - use_ordered_dict=False, sort_ordered_dict=False): +def get_equivalence_classes(iterable, key=None, *, + big_container=dict, small_container=set): ''' Divide items in `iterable` to equivalence classes, using the key function. @@ -91,63 +91,74 @@ def get_equivalence_classes(iterable, key=None, container=set, *, >>> get_equivalence_classes({1: 2, 3: 4, 'meow': 2}) {2: {1, 'meow'}, 4: {3}} - - If you'd like the result to be in an `OrderedDict`, specify - `use_ordered_dict=True`, and the items will be ordered according to - insertion order. If you'd like that `OrderedDict` to be sorted, pass in - `sort_ordered_dict=True`. (It automatically implies - `use_ordered_dict=True`.) You can also pass in a sorting key function or - attribute name as the `sort_ordered_dict` argument. + You can use optional arguments `small_container` and `big_container` to + customize the containers used in the result. `big_container` is `dict` by + default, but you can use alternative dict types like `OrderedDict`, + `defaultdict`, `SortedDict` or any other kind of mapping. Example: + + >>> from python_toolbox.nifty_collections import OrderedDict + >>> get_equivalence_classes(range(10), lambda x: x % 3, + big_container=OrderedDict) + OrderedDict([(0, {0, 9, 3, 6}), (1, {1, 4, 7}), (2, {8, 2, 5})]) + + You can pass in either the type of mapping, or an existing instance, and + then the existing items will still be there and have the equivalence + classes added to them. + + `small_container` is the container in which the items are put inside the + mapping. By default it's `set` but you can specify any other kind of + container. If you something like `OrderedSet`, the items will be inserted + in the same order that they were in the original `iterable`. Example: + + >>> get_equivalence_classes(range(10), lambda x: x % 3, + small_container=tuple) + {0: (0, 3, 6, 9), 1: (1, 4, 7), 2: (2, 5, 8)} + ''' - from python_toolbox import comparison_tools + from python_toolbox import nifty_collections - ### Pre-processing input: ################################################# - # # + if isinstance(big_container, collections.abc.Mapping): + big_container_type = type(big_container) + big_container_instance = big_container + else: + big_container_type = big_container + big_container_instance = None + assert issubclass(big_container_type, collections.abc.Mapping) + + assert issubclass(small_container, collections.abc.Iterable) if key is None: - if isinstance(iterable, collections.Mapping): - d = iterable + if isinstance(iterable, collections.abc.Mapping): + items = iterable.items() else: - try: - d = dict(iterable) - except ValueError: - raise Exception( - "You can't put in a non-dict without also supplying a " - "`key` function. We need to know which key to use." - ) + raise Exception( + "You can't put in a non-dict without also supplying a " + "`key` function. We need to know which key to use." + ) else: # key is not None assert cute_iter_tools.is_iterable(iterable) key_function = comparison_tools.process_key_function_or_attribute_name( key ) - d = {key: key_function(key) for key in iterable} - # # - ### Finished pre-processing input. ######################################## - - if use_ordered_dict or sort_ordered_dict: - from python_toolbox import nifty_collections - new_dict = nifty_collections.OrderedDict() + items = ((key, key_function(key)) for key in iterable) + + # If we know our big container isn't ordered, we can save some performance + # and use a dict as our pre-dict, otherwise play it safe and use an ordered + # dict. + pre_dict = ({} if big_container_type in {dict, collections.defaultdict} + else nifty_collections.OrderedDict()) + for key, value in items: + pre_dict.setdefault(value, []).append(key) + + if big_container_instance is None: + big_container_instance = big_container_type( + ((key, small_container(value)) for key, value in pre_dict.items()) + ) else: - new_dict = {} - for key, value in d.items(): - new_dict.setdefault(value, []).append(key) - - # Making into desired container: - for key, value in new_dict.copy().items(): - new_dict[key] = container(value) + for key, value in pre_dict.items(): + big_container_instance[key] = small_container(value) - if sort_ordered_dict: - if isinstance(sort_ordered_dict, (collections.Callable, str)): - key_function = comparison_tools. \ - process_key_function_or_attribute_name(sort_ordered_dict) - new_dict.sort(key_function) - elif sort_ordered_dict is True: - new_dict.sort() - return new_dict - - else: - return new_dict - + return big_container_instance def logic_max(iterable, relation=lambda a, b: (a >= b)): diff --git a/source_py3/python_toolbox/math_tools/factorials.py b/source_py3/python_toolbox/math_tools/factorials.py index 22be704bb..18acdff27 100644 --- a/source_py3/python_toolbox/math_tools/factorials.py +++ b/source_py3/python_toolbox/math_tools/factorials.py @@ -76,7 +76,7 @@ def from_factoradic(factoradic_number): ''' from python_toolbox import sequence_tools - assert isinstance(factoradic_number, collections.Iterable) + assert isinstance(factoradic_number, collections.abc.Iterable) factoradic_number = \ sequence_tools.ensure_iterable_is_sequence(factoradic_number) number = 0 diff --git a/source_py3/python_toolbox/math_tools/misc.py b/source_py3/python_toolbox/math_tools/misc.py index 6ab475b6a..3051cedbb 100644 --- a/source_py3/python_toolbox/math_tools/misc.py +++ b/source_py3/python_toolbox/math_tools/misc.py @@ -4,13 +4,29 @@ import numbers import math import random +import decimal as decimal_module import python_toolbox.cute_enum infinity = float('inf') infinities = (infinity, -infinity) - +pi_decimal = decimal_module.Decimal( + '3.14159265358979323846264338327950288419716939937510582097494459230781640' + '6286208998628034825342117067982148086513282306647093844609550582231725359' + '4081284811174502841027019385211055596446229489549303819644288109756659334' + '4612847564823378678316527120190914564856692346034861045432664821339360726' + '0249141273724587006606315588174881520920962829254091715364367892590360011' + '3305305488204665213841469519415116094330572703657595919530921861173819326' + '1179310511854807446237996274956735188575272489122793818301194912983367336' + '2440656643086021394946395224737190702179860943702770539217176293176752384' + '6748184676694051320005681271452635608277857713427577896091736371787214684' + '4090122495343014654958537105079227968925892354201995611212902196086403441' + '8159813629774771309960518707211349999998372978049951059731732816096318595' + '0244594553469083026425223082533446850352619311881710100031378387528865875' + '3320838142061717766914730359825349042875546873115956286388235378759375195' + '7781857780532171226806613001927876611195909216420199' +) def cute_floor_div(x, y): ''' @@ -227,4 +243,4 @@ def cute_round(x, round_mode=RoundMode.CLOSEST_OR_DOWN, *, step=1): round_up = random.random() < mod / step return (div + round_up) * step - \ No newline at end of file + diff --git a/source_py3/python_toolbox/misc_tools/misc_tools.py b/source_py3/python_toolbox/misc_tools/misc_tools.py index 90a90fa90..97a0f980f 100644 --- a/source_py3/python_toolbox/misc_tools/misc_tools.py +++ b/source_py3/python_toolbox/misc_tools/misc_tools.py @@ -353,3 +353,35 @@ def __bool__(self): from python_toolbox import sequence_tools return bool(sequence_tools.get_length(self)) +def phrase_iterable_in_english(iterable): + ''' + Get a nice textual output for a bunch of items. + + Example: + + >>> phrase_iterable_in_english(('foo', 'bar', 'baz)) + 'foo, bar and baz' + + ''' + + from python_toolbox import sequence_tools + from python_toolbox import combi + sequence = combi.MapSpace(str, iterable) + if len(sequence) == 0: + return '' + elif len(sequence) == 1: + (item,) = sequence + return item + elif len(sequence) == 2: + return ' and '.join(sequence) + else: + assert len(sequence) >= 3 + return '%s and %s' % (', '.join(sequence[:-1]), sequence[-1]) + + + + + + + + \ No newline at end of file diff --git a/source_py3/python_toolbox/multiprocessing_tools/__init__.py b/source_py3/python_toolbox/multiprocessing_tools/__init__.py new file mode 100644 index 000000000..39c1bbf18 --- /dev/null +++ b/source_py3/python_toolbox/multiprocessing_tools/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. diff --git a/source_py3/python_toolbox/nifty_collections/__init__.py b/source_py3/python_toolbox/nifty_collections/__init__.py index e7e3c758a..5cc24f4b9 100644 --- a/source_py3/python_toolbox/nifty_collections/__init__.py +++ b/source_py3/python_toolbox/nifty_collections/__init__.py @@ -3,16 +3,19 @@ '''Defines various data types, similarly to the stdlib's `collections`.''' -from .ordered_dict import OrderedDict from .various_ordered_sets import OrderedSet, FrozenOrderedSet, EmittingOrderedSet from .weak_key_default_dict import WeakKeyDefaultDict from .weak_key_identity_dict import WeakKeyIdentityDict from .lazy_tuple import LazyTuple -from .various_frozen_dicts import FrozenDict, FrozenOrderedDict from .bagging import Bag, OrderedBag, FrozenBag, FrozenOrderedBag from .frozen_bag_bag import FrozenBagBag +from .condition_list import ConditionList from ..cute_enum import CuteEnum - from .emitting_weak_key_default_dict import EmittingWeakKeyDefaultDict +from .nifty_dicts import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, FrozenOrderedDict, + DoubleFrozenOrderedDict +) from .abstract import Ordered, DefinitelyUnordered \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/abstract.py b/source_py3/python_toolbox/nifty_collections/abstract.py index b39b823ca..14692d785 100644 --- a/source_py3/python_toolbox/nifty_collections/abstract.py +++ b/source_py3/python_toolbox/nifty_collections/abstract.py @@ -2,7 +2,7 @@ # This program is distributed under the MIT license. import abc -import collections +import collections.abc import queue import multiprocessing.queues @@ -20,7 +20,7 @@ class Ordered(metaclass=abc.ABCMeta): __slots__ = () -Ordered.register(collections.Sequence) +Ordered.register(collections.abc.Sequence) Ordered.register(collections.OrderedDict) Ordered.register(collections.deque) Ordered.register(queue.Queue) @@ -28,6 +28,14 @@ class Ordered(metaclass=abc.ABCMeta): ############################################################################### +class OrderedMapping(Ordered, collections.abc.Mapping): + '''blocktododoc''' + __slots__ = () + +Ordered.register(collections.OrderedDict) + +############################################################################### + class DefinitelyUnordered(metaclass=abc.ABCMeta): ''' A data structure that does not have a defined order. @@ -40,8 +48,7 @@ class DefinitelyUnordered(metaclass=abc.ABCMeta): @classmethod def __subclasshook__(cls, type_): - if cls is DefinitelyUnordered and \ - issubclass(type_, collections.OrderedDict): + if cls is DefinitelyUnordered and issubclass(type_, Ordered): return False else: return NotImplemented diff --git a/source_py3/python_toolbox/nifty_collections/bagging.py b/source_py3/python_toolbox/nifty_collections/bagging.py index 11ebd20f5..c66f9e642 100644 --- a/source_py3/python_toolbox/nifty_collections/bagging.py +++ b/source_py3/python_toolbox/nifty_collections/bagging.py @@ -13,9 +13,8 @@ from python_toolbox import math_tools from .lazy_tuple import LazyTuple -from .ordered_dict import OrderedDict from .various_ordered_sets import FrozenOrderedSet -from .various_frozen_dicts import FrozenDict, FrozenOrderedDict +from .nifty_dicts import FrozenDict, FrozenOrderedDict, OrderedDict from .abstract import Ordered, DefinitelyUnordered @@ -148,7 +147,7 @@ class _BaseBagMixin: def __init__(self, iterable={}): super().__init__() - if isinstance(iterable, collections.Mapping): + if isinstance(iterable, collections.abc.Mapping): for key, value, in iterable.items(): try: self._dict[key] = _process_count(value) @@ -824,7 +823,7 @@ def get_contained_bags(self): -class _BaseDictDelegator(collections.MutableMapping): +class _BaseDictDelegator(collections.abc.MutableMapping): ''' Base class for a dict-like object. diff --git a/source_py3/python_toolbox/nifty_collections/condition_list.py b/source_py3/python_toolbox/nifty_collections/condition_list.py new file mode 100644 index 000000000..5a67c519a --- /dev/null +++ b/source_py3/python_toolbox/nifty_collections/condition_list.py @@ -0,0 +1,120 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import threading + +import collections.abc + +from python_toolbox import context_management + + +class ConditionList(collections.abc.MutableSequence, + context_management.DelegatingContextManager): + ''' + Thread synchronization tool. + + This is a complex tool for synchronizing threads, so let me explain how it + works. + + `ConditionList` is similar to a Python `list`. You can add items to it, + view the items in it, change their order, remove items, etc. + + What makes it different than a normal `list` is that it provides methods + `wait_for` and `wait_for_missing` that let you orchestrate threads based on + this list. + + `wait_for` receives a list of items (or just one) and makes the thread + block until the `ConditionList` object contains all of these items, and + then the thread results. `wait_for_missing` is the opposite, waiting for a + specified list of items to *not* be in the `ConditionList`, and only then + does the thread resume. (See the docstrings for these functions for more + details about the arguments.) + + When is `ConditionList` useful? Maybe you have a thread that's + + blocktododoc + ''' + + def __init__(self, iterable=None): + self.__list = list(iterable) if iterable is not None else [] + self.__condition = \ + self.delegatee_context_manager = threading.Condition() + + + def __setitem__(self, index, value): + if isinstance(index, slice): + raise NotImplementedError("Setting a slice isn't implemented yet.") + assert isinstance(index, int) + with self.__condition: + self.__list[index] = value + self.__notify_all() + + + def __delitem__(self, index): + with self.__condition: + del self.__list[index] + self.__notify_all() + + def insert(self, index, value): + assert isinstance(index, int) + with self.__condition: + self.__list.insert(index, value) + self.__notify_all() + + def __getitem__(self, index): + with self.__condition: + return self.__list[index] + + def __len__(self): + with self.__condition: + return len(self.__list) + + + def __notify_all(self): + with self.__condition: + self.__condition.notify_all() + + def wait(self, *, timeout=None): + from python_toolbox import sequence_tools + with self.__condition: + self.__condition.wait(timeout=timeout) + + def wait_for(self, *items, remove=False, timeout=None, + extra_predicate=lambda: True): + from python_toolbox import sequence_tools + with self.__condition: + self.__condition.wait_for( + lambda: (extra_predicate() and + sequence_tools.is_contained_in(items, self.__list)) + ) + if remove: + sequence_tools.remove_items(items, self) + + def wait_for_missing(self, *missing_items, timeout=None, + extra_predicate=lambda: True): + from python_toolbox import sequence_tools + with self.__condition: + self.__condition.wait_for( + lambda: (extra_predicate() and + not sequence_tools.is_contained_in(missing_items, + self.__list)) + ) + + def wait_for_empty(self, timeout=None, extra_predicate=lambda: True): + from python_toolbox import sequence_tools + with self.__condition: + self.__condition.wait_for( + lambda: (extra_predicate() and not self.__list) + ) + + def play_out(self, iterable): + assert not self + for item in iterable: + self.append(item) + self.wait_for_missing(item) + + + def __repr__(self): + return '<%s: %s>' % (type(self).__name__, self.__list) + + \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/lazy_tuple.py b/source_py3/python_toolbox/nifty_collections/lazy_tuple.py index 86469e056..1bf2593c5 100644 --- a/source_py3/python_toolbox/nifty_collections/lazy_tuple.py +++ b/source_py3/python_toolbox/nifty_collections/lazy_tuple.py @@ -45,7 +45,7 @@ def _with_lock(method, *args, **kwargs): @functools.total_ordering -class LazyTuple(collections.Sequence): +class LazyTuple(collections.abc.Sequence): ''' A lazy tuple which requests as few values as possible from its iterator. @@ -75,8 +75,10 @@ def my_generator(): ''' def __init__(self, iterable, definitely_infinite=False): - was_given_a_sequence = isinstance(iterable, collections.Sequence) and \ - not isinstance(iterable, LazyTuple) + was_given_a_sequence = ( + isinstance(iterable, collections.abc.Sequence) and + not isinstance(iterable, LazyTuple) + ) self.is_exhausted = True if was_given_a_sequence else False '''Flag saying whether the internal iterator is tobag exhausted.''' diff --git a/source_py3/python_toolbox/nifty_collections/nifty_dicts/__init__.py b/source_py3/python_toolbox/nifty_collections/nifty_dicts/__init__.py new file mode 100644 index 000000000..15da0d2ce --- /dev/null +++ b/source_py3/python_toolbox/nifty_collections/nifty_dicts/__init__.py @@ -0,0 +1,5 @@ +from .nifty_dicts import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, FrozenOrderedDict, + DoubleFrozenOrderedDict +) \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/nifty_dicts/abstract.py b/source_py3/python_toolbox/nifty_collections/nifty_dicts/abstract.py new file mode 100644 index 000000000..791921411 --- /dev/null +++ b/source_py3/python_toolbox/nifty_collections/nifty_dicts/abstract.py @@ -0,0 +1,188 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections +import operator +import functools +import itertools + +from python_toolbox import comparison_tools + +from python_toolbox.nifty_collections.abstract import (Ordered, OrderedMapping, + DefinitelyUnordered) +from .ordered_dict import OrderedDict + + +class _AbstractMappingDelegator(collections.abc.Mapping): + def __init__(self, *args, **kwargs): + self._dict = self._dict_type(*args, **kwargs) + + __getitem__ = lambda self, key: self._dict[key] + __len__ = lambda self: len(self._dict) + __iter__ = lambda self: iter(self._dict) + + def copy(self, *args, **kwargs): + base_dict = self._dict.copy() + base_dict.update(*args, **kwargs) + return type(self)(base_dict) + + __repr__ = lambda self: '%s(%s)' % (type(self).__name__, + repr(self._dict) if self._dict else '') + __reduce__ = lambda self: (self.__class__ , (self._dict,)) + + +class _AbstractMutableMappingDelegator(_AbstractMappingDelegator, + collections.abc.MutableMapping): + def __setitem__(self, key, value): + self._dict[key] = value + + def __delitem__(self, key): + del self._dict[key] + + +class _AbstractFrozenDict(_AbstractMappingDelegator): + _hash = None # Overridden by instance when calculating hash. + + def __hash__(self): + if self._hash is None: + self._hash = functools.reduce( + operator.xor, + map( + hash, + itertools.chain( + (h for h in self.items()), + (type(self), len(self)) + ) + ), + 0 + ) + + return self._hash + + +class BaseDoubleDict(_AbstractMappingDelegator): + # This has a different name, and we're exposing it too, so people could do + # `isinstance(d, BaseDoubleDict)` and get `True` if it's either a + # `DoubleDict`, `DoubleFrozenDict` or `DoubleFrozenOrderedDict` or + # `DoubleFrozenOrderedDict`. + def __init__(self, *args, **kwargs): + if hasattr(self, '_dict'): + assert self.inverse.inverse is self + else: + self._dict = self._dict_type(*args, **kwargs) + internal_inverse = self._dict_type((value, key) for key, value + in self._dict.items()) + if len(internal_inverse) != len(self._dict): + raise ValueError("There's a repeating value given to the " + "double-sided dict, that is not allowed.") + assert len(internal_inverse) == len(self._dict) + self.inverse = type(self).__new__(type(self)) + self.inverse._dict = internal_inverse + self.inverse.inverse = self + self.inverse.__init__() + + +class _AbstractMutableDoubleDict(BaseDoubleDict, + collections.abc.MutableMapping): + + def _assert_valid(self): + assert len(self._dict) == len(self.inverse._dict) + + def __setitem__(self, key, value): + self._assert_valid() + try: + existing_key = self.inverse[value] + except KeyError: + pass + except TypeError as hashing_error: + raise TypeError( + "%s is not hashable so it can't be used as a value in a " + "double-sided dict." % value + ) from hashing_error + else: + raise ValueError( + "Can't add key %s with value %s because there is already a " + "key %s with the same value." % (key, value, + self.inverse[value]) + ) + + try: + existing_value = self[key] + except KeyError: + got_existing_value = False + else: + got_existing_value = True + + self._dict[key] = value + self.inverse._dict[value] = key + if got_existing_value: + del self.inverse._dict[existing_value] + self._assert_valid() + + + def __delitem__(self, key): + value = self[key] # Propagating KeyError + self._assert_valid() + del self._dict[key] + del self.inverse._dict[value] + self._assert_valid() + + + def clear(self): + self._assert_valid() + self._dict.clear() + self.inverse._dict.clear() + self._assert_valid() + + +class _UnorderedDictDelegator(DefinitelyUnordered, + _AbstractMappingDelegator): + + _dict_type = dict + + +class _OrderedDictDelegator(OrderedMapping, _AbstractMappingDelegator): + + _dict_type = OrderedDict + def __reversed__(self): + return reversed(self._dict) + + def index(self, key): + '''Get the index number of `key`.''' + if key not in self._dict: + raise ValueError + for i, key_ in enumerate(self._dict): + if key_ == key: + return i + raise RuntimeError + + +class _MutableOrderedDictDelegator(_OrderedDictDelegator, + _AbstractMutableMappingDelegator): + + + def sort(self, key=None, reverse=False): + ''' + Sort the items according to their keys, changing the order in-place. + + The optional `key` argument, (not to be confused with the dictionary + keys,) will be passed to the `sorted` function as a key function. + ''' + key_function = \ + comparison_tools.process_key_function_or_attribute_name(key) + sorted_keys = sorted(self._dict.keys(), key=key_function, + reverse=reverse) + for key_ in sorted_keys[1:]: + self.move_to_end(key_) + + + def move_to_end(self, key, last=True): + ''' + Move an existing element to the end (or beginning if `last is False`.) + + Raises `KeyError` if the element does not exist. + + When `last is True`, acts like a fast version of `self[key] = + self.pop(key)`. + ''' + self._dict.move_to_end(key, last=last) \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/nifty_dicts/nifty_dicts.py b/source_py3/python_toolbox/nifty_collections/nifty_dicts/nifty_dicts.py new file mode 100644 index 000000000..fa823b7bc --- /dev/null +++ b/source_py3/python_toolbox/nifty_collections/nifty_dicts/nifty_dicts.py @@ -0,0 +1,124 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections +import operator +import functools +import itertools + +from python_toolbox import comparison_tools + +from .ordered_dict import OrderedDict +from . import abstract + + +class DoubleDict(abstract._UnorderedDictDelegator, + abstract._AbstractMutableDoubleDict): + ''' + blocktododoc''' + + +class FrozenDict(abstract._UnorderedDictDelegator, + abstract._AbstractFrozenDict): + ''' + An immutable `dict`. + + A `dict` that can't be changed. The advantage of this over `dict` is mainly + that it's hashable, and thus can be used as a key in dicts and sets. + + In other words, `FrozenDict` is to `dict` what `frozenset` is to `set`. + ''' + + +class DoubleFrozenDict(abstract._UnorderedDictDelegator, + abstract.BaseDoubleDict, + abstract._AbstractFrozenDict): + '''blocktododoc''' + + +class DoubleOrderedDict(abstract._OrderedDictDelegator, + abstract._AbstractMutableDoubleDict): + '''blocktododoc''' + + + def sort(self, *, key=None, reverse=False): + ''' + Sort the items according to their keys, changing the order in-place. + + The optional `key` argument, (not to be confused with the dictionary + keys,) will be passed to the `sorted` function as a key function. + ''' + from python_toolbox import comparison_tools + key_function = \ + comparison_tools.process_key_function_or_attribute_name(key) + sorted_keys = sorted(self.keys(), key=key_function, reverse=reverse) + for key_ in sorted_keys[1:]: + self.move_to_end(key_) + + + def move_to_end(self, key, *, last=True): + ''' + Move an existing element to the end (or beginning if `last is False`.) + + Raises `KeyError` if the element does not exist. + + When `last is True`, acts like a fast version of `self[key] = + self.pop(key)`. + ''' + self._assert_valid() + value = self._dict[key] # Propagate `KeyError`. + self._dict.move_to_end(key, last=last) + self.inverse._dict.move_to_end(value, last=last) + self._assert_valid() + + +class FrozenOrderedDict(abstract._OrderedDictDelegator, + abstract._AbstractFrozenDict): + ''' + An immutable, ordered `dict`. + + A `dict` that is ordered and can't be changed. The advantage of this over + `OrderedDict` is mainly that it's hashable, and thus can be used as a key + in dicts and sets. + ''' + _dict_type = OrderedDict + + def __eq__(self, other): + if isinstance(other, (OrderedDict, FrozenOrderedDict)): + return collections.abc.Mapping.__eq__(self, other) and \ + all(map(operator.eq, self, other)) + return collections.abc.Mapping.__eq__(self, other) + + __hash__ = abstract._AbstractFrozenDict.__hash__ + # (Gotta manually carry `__hash__` over from the base class because setting + # `__eq__` resets it. ) + + + # Poor man's caching because we can't import `CachedProperty` due to import + # loop: + _reversed = None + def __reversed__(self): + ''' + Get a version of this `FrozenOrderedDict` with key order reversed. + ''' + if self._reversed is None: + self._reversed = type(self)(reversed(tuple(self.items()))) + return self._reversed + + def __repr__(self): + if self._dict: + inner = '[%s]' % ', '.join( + '(%s, %s)' % item for item in self._dict.items() + ) + else: + inner = '' + return '%s(%s)' % (type(self).__name__, inner) + + + +class DoubleFrozenOrderedDict(abstract._OrderedDictDelegator, + abstract.BaseDoubleDict, + abstract._AbstractFrozenDict): + '''blocktododoc''' + + diff --git a/source_py3/python_toolbox/nifty_collections/ordered_dict.py b/source_py3/python_toolbox/nifty_collections/nifty_dicts/ordered_dict.py similarity index 81% rename from source_py3/python_toolbox/nifty_collections/ordered_dict.py rename to source_py3/python_toolbox/nifty_collections/nifty_dicts/ordered_dict.py index f188cd725..fe9d5496b 100644 --- a/source_py3/python_toolbox/nifty_collections/ordered_dict.py +++ b/source_py3/python_toolbox/nifty_collections/nifty_dicts/ordered_dict.py @@ -5,8 +5,10 @@ from collections import OrderedDict as StdlibOrderedDict +from ..abstract import OrderedMapping -class OrderedDict(StdlibOrderedDict): + +class OrderedDict(StdlibOrderedDict, OrderedMapping): ''' A dictionary with an order. @@ -14,7 +16,7 @@ class OrderedDict(StdlibOrderedDict): improvements. ''' - def sort(self, key=None, reverse=False): + def sort(self, *, key=None, reverse=False): ''' Sort the items according to their keys, changing the order in-place. @@ -36,8 +38,4 @@ def index(self, key): if key_ == key: return i raise RuntimeError - - @property - def reversed(self): - '''Get a version of this `OrderedDict` with key order reversed.''' - return type(self)(reversed(tuple(self.items()))) \ No newline at end of file + \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/various_frozen_dicts.py b/source_py3/python_toolbox/nifty_collections/various_frozen_dicts.py deleted file mode 100644 index 5f7c96559..000000000 --- a/source_py3/python_toolbox/nifty_collections/various_frozen_dicts.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2009-2017 Ram Rachum. -# This program is distributed under the MIT license. - -import collections -import operator -import functools -import itertools - -from .abstract import Ordered, DefinitelyUnordered -from .ordered_dict import OrderedDict - - -class _AbstractFrozenDict(collections.Mapping): - _hash = None # Overridden by instance when calculating hash. - - def __init__(self, *args, **kwargs): - self._dict = self._dict_type(*args, **kwargs) - - __getitem__ = lambda self, key: self._dict[key] - __len__ = lambda self: len(self._dict) - __iter__ = lambda self: iter(self._dict) - - def copy(self, *args, **kwargs): - base_dict = self._dict.copy() - base_dict.update(*args, **kwargs) - return type(self)(base_dict) - - def __hash__(self): - if self._hash is None: - self._hash = functools.reduce( - operator.xor, - map( - hash, - itertools.chain( - (h for h in self.items()), - (type(self), len(self)) - ) - ), - 0 - ) - - return self._hash - - __repr__ = lambda self: '%s(%s)' % (type(self).__name__, - repr(self._dict)) - __reduce__ = lambda self: (self.__class__ , (self._dict,)) - - -class FrozenDict(DefinitelyUnordered, _AbstractFrozenDict): - ''' - An immutable `dict`. - - A `dict` that can't be changed. The advantage of this over `dict` is mainly - that it's hashable, and thus can be used as a key in dicts and sets. - - In other words, `FrozenDict` is to `dict` what `frozenset` is to `set`. - ''' - _dict_type = dict - - -class FrozenOrderedDict(Ordered, _AbstractFrozenDict): - ''' - An immutable, ordered `dict`. - - A `dict` that is ordered and can't be changed. The advantage of this over - `OrderedDict` is mainly that it's hashable, and thus can be used as a key - in dicts and sets. - ''' - _dict_type = OrderedDict - - def __eq__(self, other): - if isinstance(other, (OrderedDict, FrozenOrderedDict)): - return collections.Mapping.__eq__(self, other) and \ - all(map(operator.eq, self, other)) - return collections.Mapping.__eq__(self, other) - - __hash__ = _AbstractFrozenDict.__hash__ - # (Gotta manually carry `__hash__` over from the base class because setting - # `__eq__` resets it. ) - - - # Poor man's caching because we can't import `CachedProperty` due to import - # loop: - _reversed = None - @property - def reversed(self): - ''' - Get a version of this `FrozenOrderedDict` with key order reversed. - ''' - if self._reversed is None: - self._reversed = type(self)(reversed(tuple(self.items()))) - return self._reversed \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/various_ordered_sets.py b/source_py3/python_toolbox/nifty_collections/various_ordered_sets.py index fbc7a423a..d8276cb09 100644 --- a/source_py3/python_toolbox/nifty_collections/various_ordered_sets.py +++ b/source_py3/python_toolbox/nifty_collections/various_ordered_sets.py @@ -10,12 +10,15 @@ from python_toolbox import caching from python_toolbox import freezing +from . import abstract + KEY, PREV, NEXT = range(3) -class BaseOrderedSet(collections.Set, collections.Sequence): +class BaseOrderedSet(collections.abc.Set, collections.abc.Sequence, + abstract.Ordered): ''' Base class for `OrderedSet` and `FrozenOrderedSet`, i.e. set with an order. @@ -29,12 +32,36 @@ def __init__(self, iterable=()): self.__add(item) def __getitem__(self, index): - for i, item in enumerate(self): - if i == index: - return item + our_length = len(self) + if isinstance(index, int): + if index < 0: + index += our_length + if not 0 <= index < our_length: + raise IndexError + for i, item in enumerate(self): + if i == index: + return item + else: + raise RuntimeError + elif isinstance(index, slice): + from python_toolbox import sequence_tools + canonical_slice = sequence_tools.CanonicalSlice(index, self) + if canonical_slice.step > 0 or canonical_slice.step is None: + return type(self)(itertools.islice(self, *canonical_slice)) + else: + reversed_slice = ( + (None if canonical_slice.start is None else + our_length - 1 - canonical_slice.start), + (None if canonical_slice.stop is None else + our_length - 1 - canonical_slice.stop), + -canonical_slice.step + ) + return type(self)(itertools.islice(reversed(self), + *reversed_slice)) + else: - raise IndexError - + raise TypeError + def __len__(self): return len(self._map) @@ -109,7 +136,7 @@ def __hash__(self): -class OrderedSet(BaseOrderedSet, collections.MutableSet): +class OrderedSet(BaseOrderedSet, collections.abc.MutableSet): ''' A `set` with an order. @@ -236,4 +263,15 @@ def __eq__(self, other): def get_without_emitter(self): '''Get a version of this ordered set without an emitter attached.''' return OrderedSet(self) - \ No newline at end of file + + def __getitem__(self, index): + if isinstance(index, slice): + raise Exception( + "We won't let you slice an `EmittingOrderedSet` because we " + "can't be sure whether you'll want it to use the same " + "emitter." + ) + else: + return super().__getitem__(index) + + \ No newline at end of file diff --git a/source_py3/python_toolbox/nifty_collections/weak_key_default_dict.py b/source_py3/python_toolbox/nifty_collections/weak_key_default_dict.py index 81f63e642..e0d270608 100644 --- a/source_py3/python_toolbox/nifty_collections/weak_key_default_dict.py +++ b/source_py3/python_toolbox/nifty_collections/weak_key_default_dict.py @@ -13,7 +13,7 @@ #todo: needs testing -class WeakKeyDefaultDict(collections.MutableMapping): +class WeakKeyDefaultDict(collections.abc.MutableMapping): ''' A weak key dictionary which can use a default factory. diff --git a/source_py3/python_toolbox/nifty_collections/weak_key_identity_dict.py b/source_py3/python_toolbox/nifty_collections/weak_key_identity_dict.py index 7722b27eb..508f1346e 100644 --- a/source_py3/python_toolbox/nifty_collections/weak_key_identity_dict.py +++ b/source_py3/python_toolbox/nifty_collections/weak_key_identity_dict.py @@ -27,7 +27,7 @@ def __hash__(self): return self._hash -class WeakKeyIdentityDict(collections.MutableMapping): +class WeakKeyIdentityDict(collections.abc.MutableMapping): """ A weak key dictionary which cares about the keys' identities. diff --git a/source_py3/python_toolbox/queue_tools.py b/source_py3/python_toolbox/queue_tools.py index 03426c877..da598e255 100644 --- a/source_py3/python_toolbox/queue_tools.py +++ b/source_py3/python_toolbox/queue_tools.py @@ -3,7 +3,6 @@ '''Defines various functions for working with queues.''' - import queue as queue_module import sys diff --git a/source_py3/python_toolbox/sequence_tools/canonical_slice.py b/source_py3/python_toolbox/sequence_tools/canonical_slice.py index 3ed36d063..d2a26152d 100644 --- a/source_py3/python_toolbox/sequence_tools/canonical_slice.py +++ b/source_py3/python_toolbox/sequence_tools/canonical_slice.py @@ -47,10 +47,10 @@ def __init__(self, slice_, iterable_or_length=None, offset=0): if isinstance(iterable_or_length, math_tools.PossiblyInfiniteIntegral): self.length = iterable_or_length - elif isinstance(iterable_or_length, collections.Sequence): + elif isinstance(iterable_or_length, collections.abc.Sequence): self.length = sequence_tools.get_length(iterable_or_length) else: - assert isinstance(iterable_or_length, collections.Iterable) + assert isinstance(iterable_or_length, collections.abc.Iterable) self.length = cute_iter_tools.get_length(iterable_or_length) else: self.length = None diff --git a/source_py3/python_toolbox/sequence_tools/misc.py b/source_py3/python_toolbox/sequence_tools/misc.py index 6cdad1e6f..e3a44723d 100644 --- a/source_py3/python_toolbox/sequence_tools/misc.py +++ b/source_py3/python_toolbox/sequence_tools/misc.py @@ -155,8 +155,8 @@ def partitions(sequence, partition_size=None, *, n_partitions=None, def is_immutable_sequence(thing): '''Is `thing` an immutable sequence, like `tuple`?''' - return isinstance(thing, collections.Sequence) and not \ - isinstance(thing, collections.MutableSequence) + return isinstance(thing, collections.abc.Sequence) and not \ + isinstance(thing, collections.abc.MutableSequence) @@ -192,7 +192,7 @@ def to_tuple(single_or_sequence, item_type=None, item_test=None): actual_item_test = None if actual_item_test is None: - if isinstance(single_or_sequence, collections.Sequence): + if isinstance(single_or_sequence, collections.abc.Sequence): return tuple(single_or_sequence) elif single_or_sequence is None: return tuple() @@ -241,13 +241,13 @@ def ensure_iterable_is_immutable_sequence(iterable, default_type=tuple, specified in `default_type`. ''' from python_toolbox import nifty_collections - assert isinstance(iterable, collections.Iterable) + assert isinstance(iterable, collections.abc.Iterable) if not allow_unordered and \ isinstance(iterable, nifty_collections.DefinitelyUnordered): raise UnorderedIterableException - if isinstance(iterable, collections.MutableSequence) or \ + if isinstance(iterable, collections.abc.MutableSequence) or \ isinstance(iterable, unallowed_types) or \ - not isinstance(iterable, collections.Sequence): + not isinstance(iterable, collections.abc.Sequence): return default_type(iterable) else: return iterable @@ -263,10 +263,10 @@ def ensure_iterable_is_sequence(iterable, default_type=tuple, makes it into a `tuple`, or into any other data type specified in `default_type`. ''' - assert isinstance(iterable, collections.Iterable) + assert isinstance(iterable, collections.abc.Iterable) if not allow_unordered and isinstance(iterable, (set, frozenset)): raise UnorderedIterableException - if isinstance(iterable, collections.Sequence) and \ + if isinstance(iterable, collections.abc.Sequence) and \ not isinstance(iterable, unallowed_types): return iterable else: @@ -285,7 +285,7 @@ def __contains__(self, item): -class CuteSequence(CuteSequenceMixin, collections.Sequence): +class CuteSequence(CuteSequenceMixin, collections.abc.Sequence): '''A sequence type that adds extra functionality.''' @@ -359,4 +359,49 @@ def is_subsequence(big_sequence, small_sequence): return True +def is_contained_in(iterable, container): + ''' + Check whether all the items in `iterable` are in `container`. + + Attempts an efficient implementation for the case where container might be + big and not optimized for lookups. + ''' + desired_items = list(iterable) + if isinstance(container, collections.abc.Iterable): + for item in container: + if item in desired_items: + desired_items.remove(item) + if not desired_items: + return True + return False + else: + return all(item in container for item in iterable) + +def remove_items(items_to_remove, mutable_sequence, *, + assert_contained_first=False): + ''' + Remove all the items in `items_to_remove` from `mutable_sequence`. + + Attempts an efficient implementation for the case where `mutable_sequence` + might be big and not optimized for looking up an item and removing it. + ''' + assert isinstance(mutable_sequence, collections.abc.MutableSequence) + items_to_remove = list(items_to_remove) + if assert_contained_first: + assert is_contained_in(items_to_remove, mutable_sequence) + indices_to_remove = [] + for i, item in enumerate(mutable_sequence): + try: + items_to_remove.remove(item) + except ValueError: + pass + else: + indices_to_remove.append(i) + if not items_to_remove: + break + else: + raise ValueError("Not all items were found, the list wasn't changed.") + + for i in reversed(indices_to_remove): + del mutable_sequence[i] \ No newline at end of file diff --git a/source_py3/python_toolbox/sleek_reffing/__init__.py b/source_py3/python_toolbox/sleek_reffing/__init__.py index 9d6b35acc..8eef9c801 100644 --- a/source_py3/python_toolbox/sleek_reffing/__init__.py +++ b/source_py3/python_toolbox/sleek_reffing/__init__.py @@ -10,8 +10,7 @@ from .sleek_ref import SleekRef from .exceptions import SleekRefDied -from .sleek_call_args import SleekCallArgs from .cute_sleek_value_dict import CuteSleekValueDict -__all__ = ['SleekRef', 'SleekRefDied', 'SleekCallArgs', 'CuteSleekValueDict'] +__all__ = ['SleekRef', 'SleekRefDied', 'CuteSleekValueDict'] diff --git a/source_py3/python_toolbox/sys_tools.py b/source_py3/python_toolbox/sys_tools.py index 0cc844f39..cb2aa8ef3 100644 --- a/source_py3/python_toolbox/sys_tools.py +++ b/source_py3/python_toolbox/sys_tools.py @@ -3,7 +3,6 @@ '''Defines various `sys`-related tools.''' - import sys try: import pathlib diff --git a/source_py3/python_toolbox/temp_file_tools.py b/source_py3/python_toolbox/temp_file_tools.py index 52c44dc41..04231e477 100644 --- a/source_py3/python_toolbox/temp_file_tools.py +++ b/source_py3/python_toolbox/temp_file_tools.py @@ -45,8 +45,13 @@ def create_temp_folder(*, prefix=tempfile.template, suffix='', create_temp_folder(chmod=0o550) ''' - temp_folder = pathlib.Path(tempfile.mkdtemp(prefix=prefix, suffix=suffix, - dir=parent_folder)) + temp_folder = pathlib.Path( + tempfile.mkdtemp( + prefix=prefix, + suffix=suffix, + dir=str(parent_folder) if parent_folder is not None else None, + ) + ) try: if chmod is not None: temp_folder.chmod(chmod) diff --git a/source_py3/python_toolbox/wx_tools/widgets/cute_window/accelerator_savvy_window.py b/source_py3/python_toolbox/wx_tools/widgets/cute_window/accelerator_savvy_window.py index 1fea691f9..06daca5f0 100644 --- a/source_py3/python_toolbox/wx_tools/widgets/cute_window/accelerator_savvy_window.py +++ b/source_py3/python_toolbox/wx_tools/widgets/cute_window/accelerator_savvy_window.py @@ -43,7 +43,7 @@ def _key_dict_to_accelerators(key_dict): ### Breaking down key tuples to individual entries: ####################### # # for key, id in original_key_dict.items(): - if isinstance(key, collections.Sequence): + if isinstance(key, collections.abc.Sequence): key_sequence = key for actual_key in key_sequence: key_dict[actual_key] = id diff --git a/source_py3/python_toolbox/wx_tools/widgets/cute_window/bind_savvy_evt_handler/name_parser.py b/source_py3/python_toolbox/wx_tools/widgets/cute_window/bind_savvy_evt_handler/name_parser.py index ddef605a0..2c92a702c 100644 --- a/source_py3/python_toolbox/wx_tools/widgets/cute_window/bind_savvy_evt_handler/name_parser.py +++ b/source_py3/python_toolbox/wx_tools/widgets/cute_window/bind_savvy_evt_handler/name_parser.py @@ -24,7 +24,8 @@ class CaseStyleType(abc.ABCMeta): class BaseCaseStyle(metaclass=CaseStyleType): '''Base class for case styles.''' - @abc_tools.AbstractStaticMethod + @staticmethod + @abc.abstractmethod def parse(name): ''' Parse a name with the given convention into a tuple of "words". diff --git a/source_py3/test_python_toolbox/test_abc_tools/test_abstract_static_method.py b/source_py3/test_python_toolbox/test_abc_tools/test_abstract_static_method.py deleted file mode 100644 index 8fd4e5d85..000000000 --- a/source_py3/test_python_toolbox/test_abc_tools/test_abstract_static_method.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2009-2017 Ram Rachum. -# This program is distributed under the MIT license. - -'''Testing module for `python_toolbox.abc_tools.AbstractStaticMethod`.''' - -import sys -import abc - -import nose - -from python_toolbox.abc_tools import AbstractStaticMethod - - -def test_instantiate_without_subclassing(): - '''Test you can't instantiate a class with an `AbstractStaticMethod`.''' - - class A(metaclass=abc.ABCMeta): - @AbstractStaticMethod - def f(): - pass - - nose.tools.assert_raises(TypeError, lambda: A()) - - -def test_override(): - ''' - Can't instantiate subclass that doesn't override `AbstractStaticMethod`. - ''' - - class B(metaclass=abc.ABCMeta): - @AbstractStaticMethod - def f(): - pass - - class C(B): - @staticmethod - def f(): - return 7 - - c = C() - - assert C.f() == c.f() == 7 - - \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_abc_tools/test_abstract_whatever.py b/source_py3/test_python_toolbox/test_abc_tools/test_abstract_whatever.py new file mode 100644 index 000000000..67e64e69a --- /dev/null +++ b/source_py3/test_python_toolbox/test_abc_tools/test_abstract_whatever.py @@ -0,0 +1,57 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import sys +import abc + +import nose + +from python_toolbox import cute_testing + +from python_toolbox.abc_tools import abstract_whatever + + +def test(): + + class A(metaclass=abc.ABCMeta): + foo = abstract_whatever() + + @abstract_whatever + def bar(self): pass + + with cute_testing.RaiseAssertor(TypeError): + A() + + + class B(A): + pass + + with cute_testing.RaiseAssertor(TypeError): + B() + + + class C(A): + foo = 7 + + with cute_testing.RaiseAssertor(TypeError): + C() + + + class D(A): + foo = 7 + bar = 9 + + D() + + + class E(A): + def foo(self): pass + def bar(self): pass + + E() + + + + + + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_caching/test_cache.py b/source_py3/test_python_toolbox/test_caching/test_cache.py index fd75c6511..ad043993e 100644 --- a/source_py3/test_python_toolbox/test_caching/test_cache.py +++ b/source_py3/test_python_toolbox/test_caching/test_cache.py @@ -50,7 +50,7 @@ class A: pass a = A() result = f(a) - assert result == f(a) == f(a) == f(a) + assert result is f(a) is f(a) is f(a) a_ref = weakref.ref(a) del a gc_tools.collect() @@ -58,13 +58,36 @@ class A: pass a = A() result = f(meow=a) - assert result == f(meow=a) == f(meow=a) == f(meow=a) + assert result is f(meow=a) is f(meow=a) is f(meow=a) a_ref = weakref.ref(a) del a gc_tools.collect() assert a_ref() is None +def test_non_weakref(): + f = cache(weakref_when_possible=False)(counting_func) + + class A: pass + + a = A() + result = f(a) + assert result == f(a) == f(a) == f(a) + a_ref = weakref.ref(a) + del a + gc_tools.collect() + assert a_ref() is not None + assert result == f(a_ref()) == f(a_ref()) == f(a_ref()) + + a = A() + result = f(meow=a) + assert result == f(meow=a) == f(meow=a) == f(meow=a) + a_ref = weakref.ref(a) + del a + gc_tools.collect() + assert a_ref() is not None + assert result == f(meow=a_ref()) == f(meow=a_ref()) == f(meow=a_ref()) + def test_lru(): '''Test the least-recently-used algorithm for forgetting cached results.''' @@ -127,19 +150,13 @@ def test_unhashable_arguments(): assert f(meow=y) == f(1, meow=y) -def test_helpful_message_when_forgetting_parentheses(): - '''Test user gets a helpful exception when when forgetting parentheses.''' +def test_error_when_forgetting_parentheses(): def confusedly_forget_parentheses(): @cache def f(): pass - with cute_testing.RaiseAssertor( - TypeError, - 'It seems that you forgot to add parentheses after `@cache` when ' - 'decorating the `f` function.' - ): - + with cute_testing.RaiseAssertor(TypeError,): confusedly_forget_parentheses() diff --git a/source_py3/test_python_toolbox/test_caching/test_cached_type.py b/source_py3/test_python_toolbox/test_caching/test_cached_type.py index 5dcb287fc..8bedc4e74 100644 --- a/source_py3/test_python_toolbox/test_caching/test_cached_type.py +++ b/source_py3/test_python_toolbox/test_caching/test_cached_type.py @@ -1,9 +1,14 @@ # Copyright 2009-2017 Ram Rachum. # This program is distributed under the MIT license. -'''Testing module for `python_toolbox.caching.CachedType`.''' +import threading +import weakref +import uuid as uuid_module -from python_toolbox.caching import CachedType +from python_toolbox import gc_tools +from python_toolbox import caching +from python_toolbox.caching import CachedType, StrongCachedType +from python_toolbox import nifty_collections def test(): @@ -14,4 +19,159 @@ def __init__(self, a=1, b=2, *args, **kwargs): assert A() is A(1) is A(b=2) is A(1, 2) is A(1, b=2) assert A() is not A(3) is not A(b=7) is not A(1, 2, 'meow') is not A(x=9) + + +def test_thread_safe(): + + class Feline(metaclass=CachedType): + def __init__(self, name): + self.name = name + self.uuid = uuid_module.uuid4().hex + self.creation_hook() + + def creation_hook(self): + condition_list.wait_for('%s_creation' % self.name, remove=True) + + condition_list = nifty_collections.ConditionList() + + class BaseThread(threading.Thread): + def __init__(self): + super().__init__() + + class FirstThread(BaseThread): + def run(self): + condition_list.wait_for('t1go', remove=True) + self.cat = Feline('cat') + condition_list.append('t1done') + + class SecondThread(BaseThread): + def run(self): + condition_list.wait_for('t2go', remove=True) + self.tiger = Feline('tiger') + condition_list.append('t2done') + + class ThirdThread(BaseThread): + def run(self): + condition_list.wait_for('t3go', remove=True) + self.cat = Feline('cat') + condition_list.append('t3done') + + class FourthThread(BaseThread): + def run(self): + condition_list.wait_for('t4go', remove=True) + self.tiger = Feline('tiger') + condition_list.append('t4done') + + threads = (FirstThread(), SecondThread(), ThirdThread(), FourthThread()) + for thread in threads: + thread.start() + + + condition_list.play_out(['t1go']) + + cache = Feline._BaseCachedType__cache + assert len(cache) == 1 + ((cat_key, cat_value),) = cache.items() + assert isinstance(cat_value, caching.cached_type.InConstructionMarker) + assert cat_value.lock.locked() + assert not Feline._BaseCachedType__quick_lock.locked() + + condition_list.play_out(['t2go']) + + assert len(cache) == 2 + ((key_1, value_1), (key_2, value_2)) = cache.items() + cat_value, tiger_value = ((value_1, value_2) if (key_1 is cat_key) + else (value_2, value_1)) + assert isinstance(cat_value, caching.cached_type.InConstructionMarker) + assert cat_value.lock.locked() + assert isinstance(tiger_value, caching.cached_type.InConstructionMarker) + assert tiger_value.lock.locked() + assert not Feline._BaseCachedType__quick_lock.locked() + + condition_list.play_out(['tiger_creation']) + condition_list.wait_for('t2done', remove=True) + + assert len(cache) == 2 + ((key_1, value_1), (key_2, value_2)) = cache.items() + cat_value, tiger_value = ((value_1, value_2) if (key_1 is cat_key) + else (value_2, value_1)) + assert isinstance(tiger_value, Feline) + assert isinstance(cat_value, caching.cached_type.InConstructionMarker) + assert cat_value.lock.locked() + assert not Feline._BaseCachedType__quick_lock.locked() + + condition_list.play_out(['t3go']) + + assert len(cache) == 2 + ((key_1, value_1), (key_2, value_2)) = cache.items() + cat_value, tiger_value = ((value_1, value_2) if (key_1 is cat_key) + else (value_2, value_1)) + assert isinstance(tiger_value, Feline) + assert isinstance(cat_value, caching.cached_type.InConstructionMarker) + assert cat_value.lock.locked() + assert not Feline._BaseCachedType__quick_lock.locked() + + condition_list.play_out(['cat_creation']) + condition_list.wait_for('t1done', 't3done', remove=True) + + assert len(cache) == 2 + ((key_1, value_1), (key_2, value_2)) = cache.items() + cat_value, tiger_value = ((value_1, value_2) if (key_1 is cat_key) + else (value_2, value_1)) + assert isinstance(tiger_value, Feline) + assert isinstance(cat_value, Feline) + + condition_list.play_out(['t4go']) + condition_list.wait_for('t4done', remove=True) + assert not condition_list + + assert len(cache) == 2 + ((key_1, value_1), (key_2, value_2)) = cache.items() + cat_value, tiger_value = ((value_1, value_2) if (key_1 is cat_key) + else (value_2, value_1)) + assert isinstance(tiger_value, Feline) + assert isinstance(cat_value, Feline) + + for thread in threads: + thread.join() + + assert threads[0].cat is threads[2].cat + assert threads[0].cat.uuid == threads[2].cat.uuid + assert threads[1].tiger is threads[3].tiger + assert threads[1].tiger.uuid == threads[3].tiger.uuid + + +def test_weakref(): + class Mouse(metaclass=CachedType): + def __init__(self, whatever): + pass + + class Cat(metaclass=StrongCachedType): + def __init__(self, whatever): + pass + + class A: pass + + assert Mouse(7) is Mouse(7) is not Mouse(8) is Mouse(8) + assert Cat(7) is Cat(7) is not Cat(8) is Cat(8) + + a = A() + a_ref = weakref.ref(a) + assert a_ref() is a + mouse = Mouse(a) + assert mouse is Mouse(a) is Mouse(a) is Mouse(a) + del a + gc_tools.collect() + assert a_ref() is None + + a = A() + a_ref = weakref.ref(a) + assert a_ref() is a + cat = Cat(a) + assert cat is Cat(a) is Cat(a) is Cat(a) + del a + gc_tools.collect() + assert a_ref() is not None + assert cat is Cat(a_ref()) is Cat(a_ref()) is Cat(a_ref()) + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_call_args/__init__.py b/source_py3/test_python_toolbox/test_call_args/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_call_args.py b/source_py3/test_python_toolbox/test_call_args/test_call_args.py similarity index 67% rename from source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_call_args.py rename to source_py3/test_python_toolbox/test_call_args/test_call_args.py index 93704e752..9ef7b5784 100644 --- a/source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_call_args.py +++ b/source_py3/test_python_toolbox/test_call_args/test_call_args.py @@ -1,23 +1,20 @@ # Copyright 2009-2017 Ram Rachum. # This program is distributed under the MIT license. -'''Testing module for `python_toolbox.sleek_reffing.SleekCallArgs`.''' - import weakref from python_toolbox import gc_tools -from python_toolbox.sleek_reffing import (SleekCallArgs, - SleekRef, - CuteSleekValueDict) -from .shared import _is_weakreffable, A, counter +from python_toolbox.function_tools import SleekCallArgs, CallArgs +from python_toolbox.sleek_reffing import SleekRef, CuteSleekValueDict +from ..test_sleek_reffing.shared import _is_weakreffable, A, counter def f(*args, **kwargs): pass def test(): - '''Test the basic workings of `SleekCallArgs`.''' + '''Test the basic workings of `SleekCallArgs` and `CallArgs`.''' sca_dict = {} args = (1, 2) @@ -34,6 +31,22 @@ def test(): gc_tools.collect() assert len(sca_dict) == 1 + ca_dict = {} + + args = (1, 2) + ca1 = CallArgs(ca_dict, f, *args) + ca_dict[ca1] = 'meow' + del args + gc_tools.collect() + assert len(ca_dict) == 1 + + args = (1, A()) + ca2 = CallArgs(ca_dict, f, *args) + ca_dict[ca2] = 'meow' + del args + gc_tools.collect() + assert len(ca_dict) == 2 + def test_unhashable(): '''Test `SleekCallArgs` on unhashable arguments.''' diff --git a/source_py3/test_python_toolbox/test_combi/test_extensive.py b/source_py3/test_python_toolbox/test_combi/test_extensive.py index c3a4f7dba..2be1482dc 100644 --- a/source_py3/test_python_toolbox/test_combi/test_extensive.py +++ b/source_py3/test_python_toolbox/test_combi/test_extensive.py @@ -48,7 +48,7 @@ def __init__(self, iterable_or_length, domain=None, n_elements=None, fixed_map={}, degrees=None, is_combination=False, slice_=None, perm_type=None): self.sequence = tuple(iterable_or_length) if \ - isinstance(iterable_or_length, collections.Iterable) else \ + isinstance(iterable_or_length, collections.abc.Iterable) else \ sequence_tools.CuteRange(iterable_or_length) self.sequence_length = len(self.sequence) self._sequence_frozen_bag = \ diff --git a/source_py3/test_python_toolbox/test_context_management/test_abstractness.py b/source_py3/test_python_toolbox/test_context_management/test_abstractness.py index f30dc88a8..ba07fd5cb 100644 --- a/source_py3/test_python_toolbox/test_context_management/test_abstractness.py +++ b/source_py3/test_python_toolbox/test_context_management/test_abstractness.py @@ -3,7 +3,6 @@ '''Module for testing the abstract methods of `ContextManager`.''' - import sys import nose diff --git a/source_py3/test_python_toolbox/test_cute_iter_tools/test_iter_with.py b/source_py3/test_python_toolbox/test_cute_iter_tools/test_iter_with.py index 76f9c0709..8316939f9 100644 --- a/source_py3/test_python_toolbox/test_cute_iter_tools/test_iter_with.py +++ b/source_py3/test_python_toolbox/test_cute_iter_tools/test_iter_with.py @@ -1,8 +1,6 @@ # Copyright 2009-2017 Ram Rachum. # This program is distributed under the MIT license. -'''Testing module for `cute_iter_tools.iter_with`.''' - import itertools from python_toolbox import nifty_collections diff --git a/source_py3/test_python_toolbox/test_cute_iter_tools/test_iterate_overlapping_subsequences.py b/source_py3/test_python_toolbox/test_cute_iter_tools/test_iterate_overlapping_subsequences.py index 347fa243e..c2647b3ed 100644 --- a/source_py3/test_python_toolbox/test_cute_iter_tools/test_iterate_overlapping_subsequences.py +++ b/source_py3/test_python_toolbox/test_cute_iter_tools/test_iterate_overlapping_subsequences.py @@ -18,7 +18,7 @@ def test_length_2(): # `iterate_overlapping_subsequences` returns an iterator, not a sequence: assert not isinstance( iterate_overlapping_subsequences(list(range(4))), - collections.Sequence + collections.abc.Sequence ) assert tuple(iterate_overlapping_subsequences(list(range(4)))) == \ diff --git a/source_py3/test_python_toolbox/test_introspection_tools/__init__.py b/source_py3/test_python_toolbox/test_function_tools/__init__.py similarity index 100% rename from source_py3/test_python_toolbox/test_introspection_tools/__init__.py rename to source_py3/test_python_toolbox/test_function_tools/__init__.py diff --git a/source_py3/test_python_toolbox/test_introspection_tools/test_get_default_args_dict.py b/source_py3/test_python_toolbox/test_function_tools/test_get_default_args_dict.py similarity index 92% rename from source_py3/test_python_toolbox/test_introspection_tools/test_get_default_args_dict.py rename to source_py3/test_python_toolbox/test_function_tools/test_get_default_args_dict.py index e35d732c4..6535d9c13 100644 --- a/source_py3/test_python_toolbox/test_introspection_tools/test_get_default_args_dict.py +++ b/source_py3/test_python_toolbox/test_function_tools/test_get_default_args_dict.py @@ -3,7 +3,7 @@ '''Testing for `python_toolbox.introspection_tools.get_default_args_dict`.''' -from python_toolbox.introspection_tools import get_default_args_dict +from python_toolbox.function_tools import get_default_args_dict from python_toolbox.nifty_collections import OrderedDict diff --git a/source_py3/test_python_toolbox/test_future_tools/test_future_tools.py b/source_py3/test_python_toolbox/test_future_tools/test_future_tools.py index 2e4a0da93..467a84392 100644 --- a/source_py3/test_python_toolbox/test_future_tools/test_future_tools.py +++ b/source_py3/test_python_toolbox/test_future_tools/test_future_tools.py @@ -1,6 +1,8 @@ # Copyright 2009-2017 Ram Rachum. # This program is distributed under the MIT license. +import os +import threading import concurrent.futures import time @@ -36,5 +38,18 @@ def sleep_and_return(seconds): as_completed=True)) == tuple(range(10)) - - \ No newline at end of file +def test_cute_dummy_executor(): + + def foo(i): + n = foo.n + foo.n += 1 + return (i, n, os.getpid(), threading.get_ident()) + foo.n = 0 + + with future_tools.CuteDummyExecutor() as executor: + results = tuple(executor.map(foo, range(5))) + zipped_results = tuple(zip(*results)) + assert zipped_results[0] == zipped_results[1] == tuple(range(5)) + assert len(set(zipped_results[2])) == 1 + assert len(set(zipped_results[3])) == 1 + diff --git a/source_py3/test_python_toolbox/test_logic_tools/test_get_equivalence_classes.py b/source_py3/test_python_toolbox/test_logic_tools/test_get_equivalence_classes.py index a3ee5b28e..703d44f8f 100644 --- a/source_py3/test_python_toolbox/test_logic_tools/test_get_equivalence_classes.py +++ b/source_py3/test_python_toolbox/test_logic_tools/test_get_equivalence_classes.py @@ -30,43 +30,48 @@ def test_iterable_input(): == {0: {1, 4}, 3: {2+3j}, -6: {5-6j}} -def test_ordered_dict_output(): - # Insertion order: +def test_big_container_small_container(): + assert ( + get_equivalence_classes( + nifty_collections.OrderedDict(((1, 2), (3, 4), ('meow', 2))), + big_container=nifty_collections.OrderedDict + ) == + get_equivalence_classes( + nifty_collections.OrderedDict(((1, 2), (3, 4), ('meow', 2))), + big_container=nifty_collections.OrderedDict() + ) == nifty_collections.OrderedDict([(2, {1, 'meow'}), (4, {3})]) + ) - assert get_equivalence_classes( - nifty_collections.OrderedDict(((1, 2), (3, 4), ('meow', 2))), - use_ordered_dict=True) == \ - nifty_collections.OrderedDict([(2, {1, 'meow'}), (4, {3})]) + assert ( + get_equivalence_classes( + nifty_collections.OrderedDict(((1, 2), (3, 4), ('meow', 2))), + big_container=dict + ) == + get_equivalence_classes( + nifty_collections.OrderedDict(((1, 2), (3, 4), ('meow', 2))), + big_container={} + ) == {2: {1, 'meow'}, 4: {3}} + ) assert get_equivalence_classes( nifty_collections.OrderedDict((('meow', 2), (1, 2), (3, 4))), - use_ordered_dict=True) == \ - nifty_collections.OrderedDict([(2, {1, 'meow'}), (4, {3})]) + big_container=nifty_collections.OrderedDict((('foo', 'bar'),))) == \ + nifty_collections.OrderedDict([('foo', 'bar'), (2, {1, 'meow'}), (4, {3})]) assert get_equivalence_classes( nifty_collections.OrderedDict(((3, 4), (1, 2), ('meow', 2))), - use_ordered_dict=True) == \ + big_container=nifty_collections.OrderedDict) == \ nifty_collections.OrderedDict([(4, {3}), (2, {1, 'meow'})]) assert get_equivalence_classes( nifty_collections.OrderedDict(((1, 2), (3, 4), ('meow', 2))), - container=tuple, - use_ordered_dict=True) == \ + small_container=tuple, + big_container=nifty_collections.OrderedDict) == \ nifty_collections.OrderedDict([(2, (1, 'meow')), (4, (3,))]) assert get_equivalence_classes( nifty_collections.OrderedDict((('meow', 2), (1, 2), (3, 4))), - container=tuple, - use_ordered_dict=True) == \ - nifty_collections.OrderedDict([(2, ('meow', 1)), (4, (3,))]) - - # Sorting: + small_container=tuple, + big_container=nifty_collections.FrozenOrderedDict) == \ + nifty_collections.FrozenOrderedDict([(2, ('meow', 1)), (4, (3,))]) - assert get_equivalence_classes({1: 2, 3: 4, 'meow': 2}, - sort_ordered_dict=True) == \ - nifty_collections.OrderedDict([(2, {1, 'meow'}), (4, {3})]) - - assert get_equivalence_classes({1: 2, 3: 4, 'meow': 2}, - sort_ordered_dict=lambda x: -x) == \ - nifty_collections.OrderedDict([(4, {3}), (2, {1, 'meow'})]) - \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_misc_tools/test_add_extension_if_plain.py b/source_py3/test_python_toolbox/test_misc_tools/test_add_extension_if_plain.py index 1306b5d19..9a5aaf6f9 100644 --- a/source_py3/test_python_toolbox/test_misc_tools/test_add_extension_if_plain.py +++ b/source_py3/test_python_toolbox/test_misc_tools/test_add_extension_if_plain.py @@ -5,13 +5,17 @@ from python_toolbox import temp_file_tools -from python_toolbox.misc_tools import add_extension_if_plain +from python_toolbox.misc_tools import phrase_iterable_in_english def test(): - assert str(add_extension_if_plain(r'''c:\hello.zip''', '.ogg')) == \ - r'''c:\hello.zip''' - assert str(add_extension_if_plain(r'''c:\hello''', '.ogg')) == \ - r'''c:\hello.ogg''' - assert str(add_extension_if_plain(r'''c:\hello''', '.mkv')) == \ - r'''c:\hello.mkv''' \ No newline at end of file + iterables_and_results = ( + ((), ''), + (['foo'], 'foo'), + ((1, 2), '1 and 2'), + ((1, 2, 'meow'), '1, 2 and meow'), + (iter((1, 2, 'meow')), '1, 2 and meow'), + ([{'a'}, {'b'}, {'c'}, {'d'}], "{'a'}, {'b'}, {'c'} and {'d'}"), + ) + for iterable, result in iterables_and_results: + assert phrase_iterable_in_english(iterable) == result \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_misc_tools/test_phrase_iterable_in_english.py b/source_py3/test_python_toolbox/test_misc_tools/test_phrase_iterable_in_english.py new file mode 100644 index 000000000..1306b5d19 --- /dev/null +++ b/source_py3/test_python_toolbox/test_misc_tools/test_phrase_iterable_in_english.py @@ -0,0 +1,17 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import nose.tools + +from python_toolbox import temp_file_tools + +from python_toolbox.misc_tools import add_extension_if_plain + + +def test(): + assert str(add_extension_if_plain(r'''c:\hello.zip''', '.ogg')) == \ + r'''c:\hello.zip''' + assert str(add_extension_if_plain(r'''c:\hello''', '.ogg')) == \ + r'''c:\hello.ogg''' + assert str(add_extension_if_plain(r'''c:\hello''', '.mkv')) == \ + r'''c:\hello.mkv''' \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_multiprocessing_tools/__init__.py b/source_py3/test_python_toolbox/test_multiprocessing_tools/__init__.py new file mode 100644 index 000000000..39c1bbf18 --- /dev/null +++ b/source_py3/test_python_toolbox/test_multiprocessing_tools/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_bagging.py b/source_py3/test_python_toolbox/test_nifty_collections/test_bagging.py index c418243e5..8b6d7752c 100644 --- a/source_py3/test_python_toolbox/test_nifty_collections/test_bagging.py +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_bagging.py @@ -87,7 +87,7 @@ def test_bool(self): assert bag assert bool(self.bag_type()) is bool(self.bag_type('')) is \ bool(self.bag_type({'d': 0,})) is False - if not isinstance(bag, collections.Hashable): + if not isinstance(bag, collections.abc.Hashable): bag.clear() assert bool(bag) is False assert not bag @@ -98,7 +98,7 @@ def test_n_elements(self): assert bag.n_elements == 4 assert bag.n_elements == 4 # Testing again because now it's a data # attribute. - if not isinstance(bag, collections.Hashable): + if not isinstance(bag, collections.abc.Hashable): bag['x'] = 1 assert bag.n_elements == 5 assert bag.n_elements == 5 @@ -108,7 +108,7 @@ def test_frozen_bag_bag(self): bag = self.bag_type('meeeow') assert bag.frozen_bag_bag == \ nifty_collections.FrozenBagBag({3: 1, 1: 3,}) - if not isinstance(bag, collections.Hashable): + if not isinstance(bag, collections.abc.Hashable): bag['o'] += 2 assert bag.frozen_bag_bag == \ nifty_collections.FrozenBagBag({3: 2, 1: 2,}) @@ -219,7 +219,7 @@ def test_ignores_zero(self): bag_1 = self.bag_type() assert bag_0 == bag_1 - if issubclass(self.bag_type, collections.Hashable): + if issubclass(self.bag_type, collections.abc.Hashable): assert hash(bag_0) == hash(bag_1) assert {bag_0, bag_1} == {bag_0} == {bag_1} @@ -227,7 +227,7 @@ def test_ignores_zero(self): self.bag_type({'a': 0.0, 'b': 2, 'c': decimal_module.Decimal('0.0'),}) bag_3 = self.bag_type('bb') - if issubclass(self.bag_type, collections.Hashable): + if issubclass(self.bag_type, collections.abc.Hashable): assert hash(bag_2) == hash(bag_3) assert {bag_2, bag_3} == {bag_2} == {bag_3} @@ -283,7 +283,7 @@ def test_operations_with_foreign_operands(self): with cute_testing.RaiseAssertor(TypeError): 'foo' ** bag with cute_testing.RaiseAssertor(TypeError): divmod(bag, 'foo') with cute_testing.RaiseAssertor(TypeError): divmod('foo', bag) - if not isinstance(bag, collections.Hashable): + if not isinstance(bag, collections.abc.Hashable): with cute_testing.RaiseAssertor(TypeError): bag |= 'foo' with cute_testing.RaiseAssertor(TypeError): bag &= 'foo' with cute_testing.RaiseAssertor(TypeError): bag += 'foo' @@ -417,7 +417,7 @@ def test_get_mutable(self): def test_get_frozen(self): bag = self.bag_type('abracadabra') frozen_bag = bag.get_frozen() - assert isinstance(frozen_bag, collections.Hashable) + assert isinstance(frozen_bag, collections.abc.Hashable) if isinstance(bag, nifty_collections.Ordered): assert tuple(bag.items()) == tuple(frozen_bag.items()) else: @@ -427,8 +427,8 @@ def test_get_frozen(self): def test_hash(self): bag = self.bag_type('abracadabra') - assert not isinstance(bag, collections.Hashable) - assert not issubclass(self.bag_type, collections.Hashable) + assert not isinstance(bag, collections.abc.Hashable) + assert not issubclass(self.bag_type, collections.abc.Hashable) with cute_testing.RaiseAssertor(TypeError): {bag} with cute_testing.RaiseAssertor(TypeError): @@ -593,7 +593,7 @@ class BaseFrozenBagTestCase(BaseBagTestCase): def test_get_mutable(self): bag = self.bag_type('abracadabra') mutable_bag = bag.get_mutable() - assert not isinstance(mutable_bag, collections.Hashable) + assert not isinstance(mutable_bag, collections.abc.Hashable) if isinstance(bag, nifty_collections.Ordered): assert tuple(bag.items()) == tuple(mutable_bag.items()) else: @@ -611,8 +611,8 @@ def test_get_frozen(self): def test_hash(self): bag = self.bag_type('abracadabra') - assert isinstance(bag, collections.Hashable) - assert issubclass(self.bag_type, collections.Hashable) + assert isinstance(bag, collections.abc.Hashable) + assert issubclass(self.bag_type, collections.abc.Hashable) assert {bag, bag} == {bag} assert {bag: bag} == {bag: bag} assert isinstance(hash(bag), int) @@ -721,7 +721,7 @@ def test_reversed(self): # Cached only for a frozen type: assert (bag.reversed is bag.reversed) == \ (bag.reversed.reversed is bag.reversed.reversed) == \ - isinstance(bag, collections.Hashable) + isinstance(bag, collections.abc.Hashable) assert bag.reversed == bag.reversed assert bag.reversed.reversed == bag.reversed.reversed @@ -741,10 +741,10 @@ def test_ordering(self): ordered_bag_0 = self.bag_type('ababb') ordered_bag_1 = self.bag_type('bbbaa') assert ordered_bag_0 == ordered_bag_0 - if issubclass(self.bag_type, collections.Hashable): + if issubclass(self.bag_type, collections.abc.Hashable): assert hash(ordered_bag_0) == hash(ordered_bag_0) assert ordered_bag_1 == ordered_bag_1 - if issubclass(self.bag_type, collections.Hashable): + if issubclass(self.bag_type, collections.abc.Hashable): assert hash(ordered_bag_1) == hash(ordered_bag_1) assert ordered_bag_0 != ordered_bag_1 assert ordered_bag_0 <= ordered_bag_1 @@ -758,7 +758,7 @@ def test_builtin_reversed(self): def test_index(self): bag = self.bag_type('aaabbc') - if not isinstance(bag, collections.Hashable): + if not isinstance(bag, collections.abc.Hashable): bag['d'] = 0 assert bag.index('a') == 0 assert bag.index('b') == 1 @@ -784,7 +784,7 @@ def test_ordering(self): bag_0 = self.bag_type('ababb') bag_1 = self.bag_type('bbbaa') assert bag_0 == bag_1 - if issubclass(self.bag_type, collections.Hashable): + if issubclass(self.bag_type, collections.abc.Hashable): assert hash(bag_0) == hash(bag_1) @@ -796,7 +796,7 @@ def test_builtin_reversed(self): def test_index(self): bag = self.bag_type('aaabbc') - if not isinstance(bag, collections.Hashable): + if not isinstance(bag, collections.abc.Hashable): bag['d'] = 0 with cute_testing.RaiseAssertor(AttributeError): bag.index('a') diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_condition_list/__init__.py b/source_py3/test_python_toolbox/test_nifty_collections/test_condition_list/__init__.py new file mode 100644 index 000000000..ed9a70da2 --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_condition_list/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_condition_list/test_condition_list.py b/source_py3/test_python_toolbox/test_nifty_collections/test_condition_list/test_condition_list.py new file mode 100644 index 000000000..1cd790170 --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_condition_list/test_condition_list.py @@ -0,0 +1,143 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import threading +import time +import queue as queue_module + +from python_toolbox import queue_tools +from python_toolbox import cute_iter_tools +from python_toolbox import sequence_tools +from python_toolbox import cute_testing + +from python_toolbox.nifty_collections import ConditionList + + +def test(): + c = ConditionList() + assert list(c) == [] + assert 7 not in c + assert len(c) == 0 + assert not c + c.append(7) + assert 7 in c + assert len(c) == 1 + assert c + c[0] = 'meow' + assert 'meow' in c + assert 7 not in c + assert len(c) == 1 + assert c + c.extend(range(3)) + assert list(c) == ['meow', 0, 1, 2] + + c = ConditionList([1, 2, 3]) + assert list(c) == [1, 2, 3] + +def test_threaded(): + + log_list = [] + log_list_lock = threading.RLock() + + c = ConditionList() + + thread_advanced_queue = queue_module.Queue() + + class Thread(threading.Thread): + is_done = False + def __init__(self, number): + super().__init__() + self.number = number + + def run(self): + thread_advanced_queue.put(self.number) + for i in range(10): + milestone = 't%sm%s' % (self.number, i) + c.is_waiting = True + c.wait_for( + milestone, + extra_predicate=lambda: (thread_advanced_queue.put(self.number) + or True) + ) + # (Used an extra predicate above just to let the main thread + # know that we done checked our predicate. This wouldn't be + # needed in real code.) + with log_list_lock: + log_list.append(milestone) + + self.is_done = True + thread_advanced_queue.put(self.number) + + + milestones_release_order = [ + 't6m3', 't9m4', 't1m4', 't1m8', 't6m0', 't7m7', 't4m4', 't5m1', 't8m8', + 't4m0', 't5m2', 't8m9', 't5m8', 't2m4', 't1m2', 't9m9', 't6m7', 't9m7', + 't1m9', 't8m3', 't6m8', 't7m5', 't5m4', 't3m6', 't0m5', 't4m2', 't6m9', + 't9m0', 't9m1', 't6m4', 't8m5', 't0m3', 't3m8', 't0m1', 't4m1', 't4m3', + 't3m3', 't1m7', 't5m0', 't8m0', 't2m1', 't6m1', 't1m1', 't9m6', 't3m7', + 't7m8', 't8m4', 't8m6', 't9m5', 't6m6', 't5m3', 't4m9', 't3m5', 't4m8', + 't0m6', 't3m1', 't0m8', 't5m5', 't4m6', 't3m0', 't7m0', 't2m5', 't5m7', + 't7m6', 't7m4', 't8m2', 't4m5', 't4m7', 't8m1', 't9m2', 't2m6', 't2m2', + 't1m6', 't2m3', 't1m5', 't2m8', 't0m0', 't6m2', 't1m3', 't7m1', 't2m0', + 't3m2', 't3m9', 't2m9', 't7m3', 't0m2', 't2m7', 't8m7', 't7m2', 't1m0', + 't0m4', 't0m9', 't0m7', 't6m5', 't9m8', 't5m6', 't3m4', 't5m9', 't7m9', + 't9m3' + ] + # (Generated randomly, keeping static here to ensure reproducible test + # runs.) + + # First we're going to calculate the + + expected_log_list = [] + milestones_in_the_bank = [set() for _ in range(10)] + def get_thread_state(number): + for i in range(0, 10): + milestones_in_the_bank_for_thread = milestones_in_the_bank[number] + if i not in milestones_in_the_bank_for_thread: + return i - 1 + return 9 + + for milestone in milestones_release_order: + thread_number = int(milestone[1]) + milestone_number = int(milestone[3]) + old_thread_state = get_thread_state(thread_number) + milestones_in_the_bank[thread_number].add(milestone_number) + new_thread_state = get_thread_state(thread_number) + for milestone_accomplished in range(old_thread_state + 1, + new_thread_state + 1): + expected_log_list.append('t%sm%s' % + (thread_number, milestone_accomplished)) + assert len(expected_log_list) == 100 + assert not sequence_tools.get_recurrences(expected_log_list) + + def wait_for_threads_to_advance(): + thread_numbers_we_have_not_seen_yet = {thread.number for thread in + threads if not thread.is_done} + while thread_numbers_we_have_not_seen_yet: + thread_numbers_we_have_not_seen_yet.discard( + thread_advanced_queue.get() + ) + + # Start your engines... + threads = tuple(map(Thread, range(10))) + for thread in threads: + thread.start() + + # Wait for all the threads to start... + wait_for_threads_to_advance() + + # And now, let the show begin! + for i, milestone in enumerate(milestones_release_order): + with log_list_lock: + assert len(log_list) <= i + c.append(milestone) + wait_for_threads_to_advance() + + # This is the money line right here: + assert log_list == expected_log_list + + + + + + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_cute_enum/test.py b/source_py3/test_python_toolbox/test_nifty_collections/test_cute_enum/test.py index e162452a4..89ff4ee2a 100644 --- a/source_py3/test_python_toolbox/test_nifty_collections/test_cute_enum/test.py +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_cute_enum/test.py @@ -39,3 +39,27 @@ class Flavor(CuteEnum): assert Flavor[:2] == (Flavor.CHOCOLATE, Flavor.VANILLA) + +def test_comparable_enum_recipe(): + class ComparableEnum(CuteEnum): + def __eq__(self, other): + if type(other) == type(self): + return self is other + elif isinstance(other, str): + return self.value == other + else: + return NotImplemented + + __str__ = lambda self: self.value + + class Style(ComparableEnum): + ROCK = 'rock' + JAZZ = 'jazz' + BLUES = 'blues' + + + assert Style.ROCK == Style.ROCK == str(Style.ROCK) == 'rock' + assert 'jazz' != Style.ROCK != Style.JAZZ + assert Style.ROCK != Style.ROCK.number == 0 + + assert sorted(Style) == [Style.ROCK, Style.JAZZ, Style.BLUES] \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_lazy_tuple/test_lazy_tuple.py b/source_py3/test_python_toolbox/test_nifty_collections/test_lazy_tuple/test_lazy_tuple.py index 6a90365c6..5fed1cd23 100644 --- a/source_py3/test_python_toolbox/test_nifty_collections/test_lazy_tuple/test_lazy_tuple.py +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_lazy_tuple/test_lazy_tuple.py @@ -15,7 +15,7 @@ from python_toolbox.nifty_collections import LazyTuple -class SelfAwareUuidIterator(collections.Iterator): +class SelfAwareUuidIterator(collections.abc.Iterator): '''Iterator that gives UUIDs and keeps them all in an internal list.''' def __init__(self): self.data = [] diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_dict_test_case.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_dict_test_case.py new file mode 100644 index 000000000..43763a84c --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_dict_test_case.py @@ -0,0 +1,78 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections.abc +import abc + +from python_toolbox.third_party import unittest2 + +import nose + +from python_toolbox import abc_tools +from python_toolbox import sequence_tools +from python_toolbox import logic_tools +from python_toolbox import cute_iter_tools +from python_toolbox import cute_testing + +from python_toolbox import nifty_collections +from python_toolbox.nifty_collections import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, + FrozenOrderedDict, DoubleFrozenOrderedDict +) + + +class AbstractDictTestCase(cute_testing.TestCase, metaclass=abc.ABCMeta): + __test__ = False + d_type = abc_tools.abstract_whatever() + + def test_mapping_base_class(self): + assert issubclass(self.d_type, collections.Mapping) + + def test_common(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + assert len(d) == 3 + assert set(d.keys()) == {1, 3, 5} + assert set(d.values()) == {2, 4, 6} + assert set(d.items()) == {(1, 2), (3, 4), (5, 6)} + assert d[1] == 2 + assert d[3] == 4 + assert d[5] == 6 + assert 1 in d + assert 'meow' not in d + with cute_testing.RaiseAssertor(exception_type=KeyError): + d[7] + with cute_testing.RaiseAssertor(exception_type=KeyError): + d[None] + with cute_testing.RaiseAssertor(exception_type=KeyError): + d['whatever'] + + assert d.get(1) == 2 + assert d.get(1, 'whatever') == 2 + assert d.get(10, 'whatever') == 'whatever' + + assert d == d.copy() == d.copy() + + assert set(d) == set(d.keys()) + assert tuple(map(set, zip(*d.items()))) == \ + (set(d.keys()), set(d.values())) + + def __init__(self, *args, **kwargs): + cute_testing.TestCase.__init__(self, *args, **kwargs) + + # Ensure no overridden test methods so no tests will go ignored: + base_classes = collections.deque(type(self).__bases__) + while base_classes: + base_class = base_classes.pop() + test_methods_from_base_classes = sequence_tools.flatten([ + [getattr(base_class_of_base_class, method) for method in + dir(base_class_of_base_class) if method.startswith('test_')] + for base_class_of_base_class in + (base_class,) + base_class.__bases__ + ]) + equivalence_classes = logic_tools.get_equivalence_classes( + test_methods_from_base_classes, key='__name__' + ) + assert set(map(len, equivalence_classes.values())) <= {1} + base_classes.extend(base_class.__bases__) + diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_one_base_test_cases.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_one_base_test_cases.py new file mode 100644 index 000000000..8715bc7bf --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_one_base_test_cases.py @@ -0,0 +1,215 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections.abc + +from python_toolbox.third_party import unittest2 +import nose + +from python_toolbox import caching +from python_toolbox import math_tools +from python_toolbox import sequence_tools +from python_toolbox import cute_iter_tools +from python_toolbox import cute_testing + +from python_toolbox import nifty_collections +from python_toolbox.nifty_collections import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, + FrozenOrderedDict, DoubleFrozenOrderedDict +) + +from tools import get_pseudo_random_strings +from abstract_dict_test_case import AbstractDictTestCase + + +class AbstractDoubleDictTestCase(AbstractDictTestCase): + def test_double_dict_base_class(self): + assert issubclass( + self.d_type, + nifty_collections.nifty_dicts.abstract.BaseDoubleDict + ) + + def test_inverse_basics(self): + d = self.d_type((('a', 'b'), ('c', 'd',), ('e', 'f',))) + inverse = d.inverse + + assert inverse.inverse is d + assert type(inverse) is type(d) + assert len(inverse) == len(d) + assert dict(inverse) == {'b': 'a', 'd': 'c', 'f': 'e',} + assert inverse['b'] == 'a' + + + def test_no_value_repeats(self): + with cute_testing.RaiseAssertor(ValueError, text='repeating value'): + self.d_type((('a', 'b'), ('c', 'd',), ('e', 'b',))) + with cute_testing.RaiseAssertor(ValueError, text='repeating value'): + self.d_type(foo=7, bar=7) + with cute_testing.RaiseAssertor(ValueError, text='repeating value'): + self.d_type({1: ('meow',), 2: ('meow',),}) + + +class AbstractNotDoubleDictTestCase(AbstractDictTestCase): + def test_not_double_dict_base_class(self): + assert not issubclass( + self.d_type, + nifty_collections.nifty_dicts.abstract.BaseDoubleDict + ) + + def test_no_inverse(self): + assert not hasattr(self.d_type(), 'inverse') + + +############################################################################### + + +class AbstractFrozenDictTestCase(AbstractDictTestCase): + def test_frozen_dict_base_class(self): + assert issubclass( + self.d_type, + collections.abc.Hashable + ) + assert not issubclass( + self.d_type, + collections.abc.MutableMapping + ) + + def test_hashable(self): + d = self.d_type(((1, 2), (3, 4))) + assert hash(d) == hash(d) + {d: 7,} + + def test_frozen(self): + d = self.d_type(((1, 2), (3, 4))) + with cute_testing.RaiseAssertor(TypeError): + d[5] = 6 + with cute_testing.RaiseAssertor(TypeError): + del d[1] + assert not hasattr(d, 'setdefault') + assert not hasattr(d, 'pop') + assert not hasattr(d, 'popitem') + assert not hasattr(d, 'update') + + + +class AbstractNotFrozenDictTestCase(AbstractDictTestCase): + def test_not_frozen_dict_base_class(self): + assert not issubclass( + self.d_type, + collections.abc.Hashable + ) + assert issubclass( + self.d_type, + collections.abc.MutableMapping + ) + + def test_not_hashable(self): + d = self.d_type(((1, 2), (3, 4))) + with cute_testing.RaiseAssertor(TypeError): + hash(d) + with cute_testing.RaiseAssertor(TypeError): + {d: 7,} + + def test_notfrozen(self): + d = self.d_type(((1, 2), (3, 4))) + assert len(d) == 2 + + d[5] = 6 + assert len(d) == 3 + + del d[5] + assert len(d) == 2 + + d.setdefault(5, 6) + assert len(d) == 3 + + value = d.pop(5) + assert value == 6 + assert len(d) == 2 + + d[5] = 6 + assert len(d) == 3 + + item = d.popitem() + assert item in {(1, 2), (3, 4), (5, 6)} + assert item[0] not in d + assert len(d) == 2 + + d.update({'foo': 'bar',}) + assert len(d) == 3 + + +############################################################################### + + +class AbstractOrderedDictTestCase(AbstractDictTestCase): + + def make_big_dict(self): + pseudo_random_strings = get_pseudo_random_strings(100) + pairs = tuple(sequence_tools.partitions(pseudo_random_strings, 2)) + d = self.d_type(pairs) + assert len(d) == 50 + assert tuple(d.items()) == pairs + return d + + + def test_ordered_dict_base_class(self): + assert issubclass( + self.d_type, + nifty_collections.abstract.Ordered + ) + assert issubclass( + self.d_type, + nifty_collections.abstract.OrderedMapping + ) + assert not issubclass( + self.d_type, + nifty_collections.abstract.DefinitelyUnordered + ) + + def test_ordered_on_long(self): + d = self.make_big_dict() + assert d.index(tuple(d.items())[7][0]) == 7 + with cute_testing.RaiseAssertor(ValueError): + d.index('meow') + + assert tuple(zip(d.keys(), d.values())) == tuple(d.items()) + + assert tuple(reversed(d)) == next(zip(*tuple(d.items())[::-1])) + + def test_index(self): + d = self.d_type(((1, 2), (3, 4))) + assert d.index(3) + with cute_testing.RaiseAssertor(ValueError): + d.index('foo') + + +class AbstractNotOrderedDictTestCase(AbstractDictTestCase): + def test_not_ordered_dict_base_class(self): + assert not issubclass( + self.d_type, + nifty_collections.abstract.Ordered + ) + assert not issubclass( + self.d_type, + nifty_collections.abstract.OrderedMapping + ) + assert issubclass( + self.d_type, + nifty_collections.abstract.DefinitelyUnordered + ) + + def test_not_ordered_on_long(self): + pseudo_random_strings = get_pseudo_random_strings(100) + pairs = sequence_tools.partitions(pseudo_random_strings, 2) + d = self.d_type(pairs) + assert len(d) == 50 + assert tuple(d.items()) != pairs + assert set(d.items()) == set(pairs) + assert not hasattr(d, 'index') + assert not hasattr(d, 'move_to_end') + assert not hasattr(d, 'sort') + assert not hasattr(d, '__reversed__') + + diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_two_base_test_cases.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_two_base_test_cases.py new file mode 100644 index 000000000..02bdfaa98 --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/abstract_two_base_test_cases.py @@ -0,0 +1,167 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections.abc + +from python_toolbox.third_party import unittest2 + +import nose + +from python_toolbox import cute_iter_tools +from python_toolbox import cute_testing + +from python_toolbox import nifty_collections +from python_toolbox.nifty_collections import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, + FrozenOrderedDict, DoubleFrozenOrderedDict +) + +from abstract_one_base_test_cases import * + + +############################################################################### + + +class AbstractDoubleFrozenDictTestCase(AbstractDoubleDictTestCase, + AbstractFrozenDictTestCase): + pass + + +class AbstractDoubleNotFrozenDictTestCase(AbstractDoubleDictTestCase, + AbstractNotFrozenDictTestCase): + def test_changing_affects_inverse(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + inverse = d.inverse + assert len(d) == len(inverse) == 3 + + d[7] = 8 + assert inverse[8] == 7 + assert len(d) == len(inverse) == 4 + + del d[3] + assert len(d) == len(inverse) == 3 + assert '4' not in inverse + + d.clear() + assert len(d) == len(inverse) == 0 + assert '2' not in inverse + + def test_del_key_error(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + del d[1] + with cute_testing.RaiseAssertor(KeyError): + del d[1] + with cute_testing.RaiseAssertor(KeyError): + del d['woof'] + + def test_cant_use_existing_value(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + with cute_testing.RaiseAssertor(ValueError, text='same value'): + d['foo'] = 4 + assert len(d) == 3 + + def test_cant_use_unhashable_value(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + with cute_testing.RaiseAssertor(TypeError, text='not hashable'): + d['foo'] = [] + assert len(d) == 3 + + + def test_change_existing_key(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + d[3] = 'foo' + assert dict(d) == {1: 2, 3: 'foo', 5: 6} + assert dict(d.inverse) == {2: 1, 'foo': 3, 6: 5} + + +class AbstractNotDoubleFrozenDictTestCase(AbstractFrozenDictTestCase, + AbstractNotDoubleDictTestCase): + pass + + +class AbstractNotDoubleNotFrozenDictTestCase(AbstractNotDoubleDictTestCase, + AbstractNotFrozenDictTestCase): + pass + + +############################################################################### + + +class AbstractDoubleOrderedDictTestCase(AbstractDoubleDictTestCase, + AbstractOrderedDictTestCase): + def test_double_ordered_without_modifying(self): + d = self.make_big_dict() + assert tuple(d.inverse.keys()) == tuple(d.values()) + assert tuple(d.inverse.values()) == tuple(d.keys()) + + some_pair = tuple(d.items())[47] + assert d.index(some_pair[0]) == 47 + assert d.inverse.index(some_pair[1]) == 47 + + +class AbstractDoubleNotOrderedDictTestCase(AbstractDoubleDictTestCase, + AbstractNotOrderedDictTestCase): + pass + + +class AbstractNotDoubleOrderedDictTestCase(AbstractOrderedDictTestCase, + AbstractNotDoubleDictTestCase): + pass + + +class AbstractNotDoubleNotOrderedDictTestCase(AbstractNotDoubleDictTestCase, + AbstractNotOrderedDictTestCase): + pass + + +############################################################################### + + +class AbstractFrozenOrderedDictTestCase(AbstractFrozenDictTestCase, + AbstractOrderedDictTestCase): + pass + + +class AbstractFrozenNotOrderedDictTestCase(AbstractFrozenDictTestCase, + AbstractNotOrderedDictTestCase): + pass + + +class AbstractNotFrozenOrderedDictTestCase(AbstractOrderedDictTestCase, + AbstractNotFrozenDictTestCase): + def test_move_to_end(self): + d = self.d_type(((1, 2), (3, 4), (5, 6))) + assert tuple(d.items()) == ((1, 2), (3, 4), (5, 6)) + assert d.index(3) == 1 + assert tuple(d) == (1, 3, 5) + + d.move_to_end(3) + assert tuple(d.items()) == ((1, 2), (5, 6), (3, 4)) + assert d.index(3) == 2 + assert tuple(d) == (1, 5, 3) + + assert d.index(5) == 1 + d.move_to_end(5, last=False) + assert tuple(d.items()) == ((5, 6), (1, 2), (3, 4)) + assert d.index(5) == 0 + assert tuple(d) == (5, 1, 3) + + def test_new_item_at_end(self): + d = self.d_type(((1, 2), (5, 6))) + d[3] = 4 + assert tuple(d.items()) == ((1, 2), (5, 6), (3, 4)) + + def test_new_item_at_end(self): + d = self.d_type(((1, 2), (5, 6), (3, 4))) + d.sort() + assert tuple(d.items()) == ((1, 2), (3, 4), (5, 6)) + d.sort(reverse=True) + assert tuple(d.items()) == ((5, 6), (3, 4), (1, 2)) + + +class AbstractNotFrozenNotOrderedDictTestCase(AbstractNotFrozenDictTestCase, + AbstractNotOrderedDictTestCase): + pass + + diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/__init__.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/__init__.py new file mode 100644 index 000000000..c512d1261 --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +1/0 # blocktodo all code from this folder should go organized into test_nifty_dicts \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_frozen_dict.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_frozen_dict.py similarity index 83% rename from source_py3/test_python_toolbox/test_nifty_collections/test_frozen_dict.py rename to source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_frozen_dict.py index 266985bcb..7fe105f96 100644 --- a/source_py3/test_python_toolbox/test_nifty_collections/test_frozen_dict.py +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_frozen_dict.py @@ -33,4 +33,15 @@ def test(): assert repr(frozen_dict).startswith('FrozenDict(') - assert pickle.loads(pickle.dumps(frozen_dict)) == frozen_dict \ No newline at end of file + assert pickle.loads(pickle.dumps(frozen_dict)) == frozen_dict + +def test_repr(): + d1 = FrozenDict(((4, 3), (2, 1))) + assert repr(d1) in {'FrozenDict({4: 3, 2: 1})', + 'FrozenDict({2: 1, 4: 3})'} + + d2 = FrozenDict() + assert repr(d2) == 'FrozenDict()' + + + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_frozen_ordered_dict.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_frozen_ordered_dict.py similarity index 93% rename from source_py3/test_python_toolbox/test_nifty_collections/test_frozen_ordered_dict.py rename to source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_frozen_ordered_dict.py index d53991326..f71c4c9cc 100644 --- a/source_py3/test_python_toolbox/test_nifty_collections/test_frozen_ordered_dict.py +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_frozen_ordered_dict.py @@ -65,3 +65,13 @@ def test_reversed(): tuple(reversed(tuple(frozen_ordered_dict.reversed.items()))) assert type(frozen_ordered_dict.reversed) is type(frozen_ordered_dict) \ is FrozenOrderedDict + +def test_repr(): + d1 = FrozenOrderedDict(((4, 3), (2, 1))) + assert repr(d1) == 'FrozenOrderedDict([(4, 3), (2, 1)])' + + d2 = FrozenOrderedDict() + assert repr(d2) == 'FrozenOrderedDict()' + + + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_ordered_dict/__init__.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_ordered_dict/__init__.py similarity index 100% rename from source_py3/test_python_toolbox/test_nifty_collections/test_ordered_dict/__init__.py rename to source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_ordered_dict/__init__.py diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_ordered_dict/test.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_ordered_dict/test.py similarity index 100% rename from source_py3/test_python_toolbox/test_nifty_collections/test_ordered_dict/test.py rename to source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_ordered_dict/test.py diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_ordered_dict/test_with_stdlib_ordered_dict.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_ordered_dict/test_with_stdlib_ordered_dict.py similarity index 100% rename from source_py3/test_python_toolbox/test_nifty_collections/test_ordered_dict/test_with_stdlib_ordered_dict.py rename to source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/old/test_ordered_dict/test_with_stdlib_ordered_dict.py diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/test_general.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/test_general.py new file mode 100644 index 000000000..6ff63d3ea --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/test_general.py @@ -0,0 +1,32 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections.abc + +from python_toolbox.third_party import unittest2 + +import nose + +from python_toolbox import cute_iter_tools +from python_toolbox import cute_testing + +from python_toolbox import nifty_collections +from python_toolbox.nifty_collections import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, + FrozenOrderedDict, DoubleFrozenOrderedDict +) + + +def test_base_double_dict(): + from nifty_collections.nifty_dicts.abstract import BaseDoubleDict + assert isinstance(DoubleDict(), BaseDoubleDict) + assert isinstance(DoubleFrozenDict(), BaseDoubleDict) + assert isinstance(DoubleOrderedDict(), BaseDoubleDict) + assert isinstance(DoubleFrozenOrderedDict(), BaseDoubleDict) + assert not isinstance({}, BaseDoubleDict) + assert not isinstance(OrderedDict(), BaseDoubleDict) + assert not isinstance(FrozenDict(), BaseDoubleDict) + assert not isinstance(FrozenOrderedDict(), BaseDoubleDict) + assert not isinstance(["haha I'm not even related"], BaseDoubleDict) + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/test_nifty_dicts.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/test_nifty_dicts.py new file mode 100644 index 000000000..f2e37ea72 --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/test_nifty_dicts.py @@ -0,0 +1,113 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +import collections.abc + +from python_toolbox.third_party import unittest2 + +import nose + +from python_toolbox import cute_iter_tools +from python_toolbox import cute_testing + +from python_toolbox import nifty_collections +from python_toolbox.nifty_collections import ( + DoubleDict, FrozenDict, OrderedDict, + DoubleFrozenDict, DoubleOrderedDict, + FrozenOrderedDict, DoubleFrozenOrderedDict +) + +from abstract_two_base_test_cases import * + + +class DoubleDictTestCase(AbstractDoubleNotFrozenDictTestCase, + AbstractDoubleNotOrderedDictTestCase, + AbstractNotFrozenNotOrderedDictTestCase): + __test__ = True + d_type = DoubleDict + + +class FrozenDictTestCase(AbstractNotDoubleFrozenDictTestCase, + AbstractFrozenNotOrderedDictTestCase, + AbstractNotDoubleNotOrderedDictTestCase): + __test__ = True + d_type = FrozenDict + + +class OrderedDictTestCase(AbstractNotDoubleOrderedDictTestCase, + AbstractNotFrozenOrderedDictTestCase, + AbstractNotDoubleNotFrozenDictTestCase): + __test__ = True + d_type = OrderedDict + + +class DoubleFrozenDictTestCase(AbstractDoubleFrozenDictTestCase, + AbstractDoubleNotOrderedDictTestCase, + AbstractFrozenNotOrderedDictTestCase): + __test__ = True + d_type = DoubleFrozenDict + + +class DoubleOrderedDictTestCase(AbstractDoubleOrderedDictTestCase, + AbstractDoubleNotFrozenDictTestCase, + AbstractNotFrozenOrderedDictTestCase): + __test__ = True + d_type = DoubleOrderedDict + + def test_changing_order_affects_double(self): + d = self.make_big_dict() + assert tuple(d.inverse.keys()) == tuple(d.values()) + assert tuple(d.inverse.values()) == tuple(d.keys()) + + old_pairs = tuple(d.items()) + d.sort() + assert set(d.items()) == set(old_pairs) + assert tuple(d.items()) != old_pairs + assert tuple(d.inverse.keys()) == tuple(d.values()) + assert tuple(d.inverse.values()) == tuple(d.keys()) + + old_pairs = tuple(d.items()) + d.inverse.sort(key=hash, reverse=True) + assert set(d.items()) == set(old_pairs) + assert tuple(d.items()) != old_pairs + assert tuple(d.inverse.keys()) == tuple(d.values()) + assert tuple(d.inverse.values()) == tuple(d.keys()) + + old_pairs = tuple(d.items()) + some_key, some_value = old_pairs[37] + d.move_to_end(some_key) + assert d.index(some_key) == len(d) - 1 + assert d.inverse.index(some_value) == len(d) - 1 + assert set(d.items()) == set(old_pairs) + assert tuple(d.items()) != old_pairs + assert tuple(d.inverse.keys()) == tuple(d.values()) + assert tuple(d.inverse.values()) == tuple(d.keys()) + + old_pairs = tuple(d.items()) + some_other_key, some_other_value = old_pairs[23] + d.inverse.move_to_end(some_other_value, last=False) + assert d.index(some_other_key) == 0 + assert d.inverse.index(some_other_value) == 0 + assert set(d.items()) == set(old_pairs) + assert tuple(d.items()) != old_pairs + assert tuple(d.inverse.keys()) == tuple(d.values()) + assert tuple(d.inverse.values()) == tuple(d.keys()) + + d['foo'] = 'bar' + assert len(d) == len(d.inverse) == len(old_pairs) + 1 + assert d.index('foo') == len(d) - 1 + assert d.inverse.index('bar') == len(d) - 1 + + +class FrozenOrderedDictTestCase(AbstractFrozenOrderedDictTestCase, + AbstractNotDoubleFrozenDictTestCase, + AbstractNotDoubleOrderedDictTestCase): + __test__ = True + d_type = FrozenOrderedDict + +class DoubleFrozenOrderedDictTestCase(AbstractDoubleFrozenDictTestCase, + AbstractDoubleOrderedDictTestCase, + AbstractFrozenOrderedDictTestCase): + __test__ = True + d_type = DoubleFrozenOrderedDict + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/tools.py b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/tools.py new file mode 100644 index 000000000..fd6e6b72b --- /dev/null +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_nifty_dicts/tools.py @@ -0,0 +1,27 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +from python_toolbox import math_tools +from python_toolbox import caching +from python_toolbox import nifty_collections +from python_toolbox import sequence_tools + + +@caching.cache() +def get_pseudo_random_strings(n): + ''' + Get a list of random-like digit strings but ensure they're always the same. + + And also they're unique, i.e. no recurrences. + ''' + some_pi_digits = str(math_tools.pi_decimal).split('.')[-1][:900] + partitions = sequence_tools.partitions(some_pi_digits, partition_size=5) + pseudo_random_numbers = nifty_collections.OrderedSet() + for partition in partitions: + if len(pseudo_random_numbers) == n: + return tuple(pseudo_random_numbers) + pseudo_random_numbers.add(partition) + else: + raise RuntimeError('Not enough unique pseudo-random numbers.') + + diff --git a/source_py3/test_python_toolbox/test_nifty_collections/test_various_ordered_sets.py b/source_py3/test_python_toolbox/test_nifty_collections/test_various_ordered_sets.py index cb4ddb493..beb797bd4 100644 --- a/source_py3/test_python_toolbox/test_nifty_collections/test_various_ordered_sets.py +++ b/source_py3/test_python_toolbox/test_nifty_collections/test_various_ordered_sets.py @@ -24,6 +24,33 @@ def test_bool(self): assert bool(self.ordered_set_type({0})) is True assert bool(self.ordered_set_type(range(5))) is True + def test_getitem(self): + s = self.ordered_set_type('abc') + assert s[0] == 'a' + assert s[1] == 'b' + assert s[2] == 'c' + assert s[-1] == 'c' + assert s[-2] == 'b' + assert s[-3] == 'a' + with cute_testing.RaiseAssertor(IndexError): + s[3] + with cute_testing.RaiseAssertor(IndexError): + s[-4] + with cute_testing.RaiseAssertor(IndexError): + s[300] + with cute_testing.RaiseAssertor(TypeError): + s['foo'] + if not issubclass(self.ordered_set_type, EmittingOrderedSet): + assert self.ordered_set_type(range(10))[7:2:-2] == \ + self.ordered_set_type([7, 5, 3]) + assert s[:] == s + assert s[:] is not s + assert s[:-1] == self.ordered_set_type('ab') + assert s[1:-1] == self.ordered_set_type('b') + assert s[-1:1:-1] == self.ordered_set_type('c') + assert s[::2] == self.ordered_set_type('ac') + assert s[::-2] == self.ordered_set_type('ca') + class BaseMutableOrderedSetTestCase(BaseOrderedSetTestCase): __test__ = False diff --git a/source_py3/test_python_toolbox/test_sequence_tools/test_canonical_slice.py b/source_py3/test_python_toolbox/test_sequence_tools/test_canonical_slice.py index 7082c5ec4..ff2c07b5f 100644 --- a/source_py3/test_python_toolbox/test_sequence_tools/test_canonical_slice.py +++ b/source_py3/test_python_toolbox/test_sequence_tools/test_canonical_slice.py @@ -23,20 +23,22 @@ def test(): slice(None, None, -2)] for slice_ in slices: - canonical_slice = CanonicalSlice(slice_) + generic_canonical_slice = CanonicalSlice(slice_) # Replacing `infinity` with huge number cause Python's lists can't # handle `infinity`: - if abs(canonical_slice.start) == infinity: - start = 10**10 * math_tools.get_sign(canonical_slice.start) - if abs(canonical_slice.stop) == infinity: - stop = 10**10 * math_tools.get_sign(canonical_slice.stop) - if abs(canonical_slice.step) == infinity: - step = 10**10 * math_tools.get_sign(canonical_slice.step) + if abs(generic_canonical_slice.start) == infinity: + start = 10**10 * math_tools.get_sign(generic_canonical_slice.start) + if abs(generic_canonical_slice.stop) == infinity: + stop = 10**10 * math_tools.get_sign(generic_canonical_slice.stop) + if abs(generic_canonical_slice.step) == infinity: + step = 10**10 * math_tools.get_sign(generic_canonical_slice.step) ####################################################################### - assert [canonical_slice.start, canonical_slice.stop, - canonical_slice.step].count(None) == 0 + assert [generic_canonical_slice.start, generic_canonical_slice.stop, + generic_canonical_slice.step].count(None) == 0 for range_ in ranges: - assert range_[slice_] == range_[canonical_slice.slice_] \ No newline at end of file + canonical_slice = CanonicalSlice(slice_, range_) + assert (range_[slice_] == range_[generic_canonical_slice.slice_] == + range_[canonical_slice.slice_]) \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_sequence_tools/test_is_contained_in.py b/source_py3/test_python_toolbox/test_sequence_tools/test_is_contained_in.py new file mode 100644 index 000000000..9787e18c2 --- /dev/null +++ b/source_py3/test_python_toolbox/test_sequence_tools/test_is_contained_in.py @@ -0,0 +1,32 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +from python_toolbox import cute_testing +from python_toolbox import sequence_tools + +from python_toolbox.sequence_tools import is_contained_in + +class PureContainer: + def __init__(self, inner): + self.inner = list(inner) + __contains__ = lambda self, item: item in self.inner + + +def test(): + true_examples = [ + ([1, 3, 6], range(10)), + (iter([1, 3, 6]), range(10)), + (range(4, 7), range(2, 11)), + (range(4, 7), PureContainer(range(2, 11))), + ] + false_examples = [ + ([11, 3, 6], range(10)), + ([1, 3, 6, 11], range(10)), + (iter([1, 3, 6, 11]), range(10)), + (range(4, 17), range(2, 11)), + (range(4, 17), PureContainer(range(2, 11))), + ] + for true_example in true_examples: + assert is_contained_in(*true_example) + for false_example in false_examples: + assert not is_contained_in(*false_example) \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_sequence_tools/test_partitions.py b/source_py3/test_python_toolbox/test_sequence_tools/test_partitions.py index cd6346602..666d6b254 100644 --- a/source_py3/test_python_toolbox/test_sequence_tools/test_partitions.py +++ b/source_py3/test_python_toolbox/test_sequence_tools/test_partitions.py @@ -102,3 +102,11 @@ def test_fill_value(): assert partitions(r, 3, fill_value=None) == [[0, 1, 2], [3, 4, None]] assert partitions([], 3, fill_value=None) == [] + +def test_whatever(): + assert partitions( + 'blablablabadbfbadfb', partition_size=5, fill_value=0 + ) == [['b', 'l', 'a', 'b', 'l'], ['a', 'b', 'l', 'a', 'b'], + ['a', 'd', 'b', 'f', 'b'], ['a', 'd', 'f', 'b', 0]] + + \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_sequence_tools/test_remove_items.py b/source_py3/test_python_toolbox/test_sequence_tools/test_remove_items.py new file mode 100644 index 000000000..e183620f6 --- /dev/null +++ b/source_py3/test_python_toolbox/test_sequence_tools/test_remove_items.py @@ -0,0 +1,16 @@ +# Copyright 2009-2017 Ram Rachum. +# This program is distributed under the MIT license. + +from python_toolbox import cute_testing +from python_toolbox import sequence_tools + +from python_toolbox.sequence_tools import remove_items + +def test(): + x = list(range(10)) + remove_items(range(4, 8), x) + assert x == [0, 1, 2, 3, 8, 9] + + x = list(range(10)) + remove_items(range(4, 8), x, assert_contained_first=True) + assert x == [0, 1, 2, 3, 8, 9] \ No newline at end of file diff --git a/source_py3/test_python_toolbox/test_sleek_reffing/test_cute_sleek_value_dict/tests.py b/source_py3/test_python_toolbox/test_sleek_reffing/test_cute_sleek_value_dict/tests.py index bb78ccfd1..cd00b2487 100644 --- a/source_py3/test_python_toolbox/test_sleek_reffing/test_cute_sleek_value_dict/tests.py +++ b/source_py3/test_python_toolbox/test_sleek_reffing/test_cute_sleek_value_dict/tests.py @@ -9,8 +9,7 @@ from python_toolbox import gc_tools -from python_toolbox.sleek_reffing import (SleekCallArgs, - SleekRef, +from python_toolbox.sleek_reffing import (SleekRef, CuteSleekValueDict) from ..shared import _is_weakreffable, A, counter diff --git a/source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_ref.py b/source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_ref.py index cfefa0781..ab2be291b 100644 --- a/source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_ref.py +++ b/source_py3/test_python_toolbox/test_sleek_reffing/test_sleek_ref.py @@ -9,10 +9,8 @@ from python_toolbox import gc_tools -from python_toolbox.sleek_reffing import (SleekCallArgs, - SleekRef, - SleekRefDied, - CuteSleekValueDict) +from python_toolbox.sleek_reffing import (SleekRef, CuteSleekValueDict, + SleekRefDied) from .shared import _is_weakreffable, A, counter @@ -35,6 +33,7 @@ def test_sleek_ref(): gc_tools.collect() assert counter() == count + 2 nose.tools.assert_raises(SleekRefDied, sleek_ref) + # I dieded. else: count = counter() del volatile_thing diff --git a/source_py3/test_python_toolbox/test_temp_file_tools/test_create_temp_folder.py b/source_py3/test_python_toolbox/test_temp_file_tools/test_create_temp_folder.py index 73477b5c5..0a3131bb2 100644 --- a/source_py3/test_python_toolbox/test_temp_file_tools/test_create_temp_folder.py +++ b/source_py3/test_python_toolbox/test_temp_file_tools/test_create_temp_folder.py @@ -116,6 +116,12 @@ def test_parent_folder(): with create_temp_folder(parent_folder=str(tf1)) as tf2: assert isinstance(tf2, pathlib.Path) assert str(tf2).startswith(str(tf1)) + +def test_parent_folder_pathlib(): + with create_temp_folder() as tf1: + with create_temp_folder(parent_folder=tf1) as tf2: + assert isinstance(tf2, pathlib.Path) + assert str(tf2).startswith(str(tf1)) def test_chmod(): with create_temp_folder(chmod=0o777) as liberal_temp_folder, \