Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pytools for openSUSE:Factory checked in at 2021-04-19 21:05:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pytools (Old) and /work/SRC/openSUSE:Factory/.python-pytools.new.12324 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytools" Mon Apr 19 21:05:51 2021 rev:13 rq:886512 version:2021.2.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pytools/python-pytools.changes 2021-01-25 18:23:55.632455402 +0100 +++ /work/SRC/openSUSE:Factory/.python-pytools.new.12324/python-pytools.changes 2021-04-19 21:06:09.492043661 +0200 @@ -1,0 +2,13 @@ +Mon Apr 19 02:01:33 UTC 2021 - Steve Kowalik <steven.kowa...@suse.com> + +- Update to 2021.2.3: + * Support pytools.tag in persistent_dict + * Add a backport of pkgutil.resolve_name + * Add persistent_dict.KeyBuilder.new_hash for hash alg customization + * Use unordered_hash in KeyBuilder hashing frozenset + * Drop dependency on, included obsolete copy of 'decorator' pypi module + * make obj_array_vectorize work on class methods +- Don't build for Python 3.6, due to no NumPy. +- Remove decorator from {Build,}Requires + +------------------------------------------------------------------- Old: ---- pytools-2021.1.tar.gz New: ---- pytools-2021.2.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pytools.spec ++++++ --- /var/tmp/diff_new_pack.GrEYwn/_old 2021-04-19 21:06:10.044044488 +0200 +++ /var/tmp/diff_new_pack.GrEYwn/_new 2021-04-19 21:06:10.044044488 +0200 @@ -18,17 +18,16 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 +%define skip_python36 1 Name: python-pytools -Version: 2021.1 +Version: 2021.2.3 Release: 0 Summary: A collection of tools for Python License: MIT -Group: Development/Languages/Python URL: https://pypi.python.org/pypi/pytools Source0: https://files.pythonhosted.org/packages/source/p/pytools/pytools-%{version}.tar.gz BuildRequires: %{python_module appdirs >= 1.4.0} BuildRequires: %{python_module base} -BuildRequires: %{python_module decorator >= 3.2.0} BuildRequires: %{python_module numpy >= 1.6.0} BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} @@ -37,7 +36,6 @@ BuildRequires: ( python3-dataclasses >= 0.7 if python3-base <= 3.6 ) BuildRequires: ( python36-dataclasses >= 0.7 if python36-base ) Requires: python-appdirs >= 1.4.0 -Requires: python-decorator >= 3.2.0 Requires: python-numpy >= 1.6.0 %if %{python_version_nodots} <= 36 Requires: python-dataclasses >= 0.7 ++++++ pytools-2021.1.tar.gz -> pytools-2021.2.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/PKG-INFO new/pytools-2021.2.3/PKG-INFO --- old/pytools-2021.1/PKG-INFO 2021-01-09 20:56:50.842988300 +0100 +++ new/pytools-2021.2.3/PKG-INFO 2021-04-05 20:37:03.446051100 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: pytools -Version: 2021.1 +Version: 2021.2.3 Summary: A collection of tools for Python Home-page: http://pypi.python.org/pypi/pytools Author: Andreas Kloeckner @@ -9,12 +9,12 @@ Description: Pytools: Lots of Little Utilities ================================= - .. image:: https://gitlab.tiker.net/inducer/pytools/badges/master/pipeline.svg + .. image:: https://gitlab.tiker.net/inducer/pytools/badges/main/pipeline.svg :alt: Gitlab Build Status - :target: https://gitlab.tiker.net/inducer/pytools/commits/master - .. image:: https://github.com/inducer/pytools/workflows/CI/badge.svg?branch=master&event=push + :target: https://gitlab.tiker.net/inducer/pytools/commits/main + .. image:: https://github.com/inducer/pytools/workflows/CI/badge.svg?branch=main&event=push :alt: Github Build Status - :target: https://github.com/inducer/pytools/actions?query=branch%3Amaster+workflow%3ACI+event%3Apush + :target: https://github.com/inducer/pytools/actions?query=branch%3Amain+workflow%3ACI+event%3Apush .. image:: https://badge.fury.io/py/pytools.png :alt: Python Package Index Release Page :target: https://pypi.org/project/pytools/ @@ -27,7 +27,6 @@ * A ton of small tool functions such as `len_iterable`, `argmin`, tuple generation, permutation generation, ASCII table pretty printing, GvR's monkeypatch_xxx() hack, the elusive `flatten`, and much more. - * Michele Simionato's decorator module * A time-series logging module, `pytools.log`. * Batch job submission, `pytools.batchjob`. * A lexer, `pytools.lex`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/README.rst new/pytools-2021.2.3/README.rst --- old/pytools-2021.1/README.rst 2020-09-30 01:40:07.000000000 +0200 +++ new/pytools-2021.2.3/README.rst 2021-04-03 00:55:56.000000000 +0200 @@ -1,12 +1,12 @@ Pytools: Lots of Little Utilities ================================= -.. image:: https://gitlab.tiker.net/inducer/pytools/badges/master/pipeline.svg +.. image:: https://gitlab.tiker.net/inducer/pytools/badges/main/pipeline.svg :alt: Gitlab Build Status - :target: https://gitlab.tiker.net/inducer/pytools/commits/master -.. image:: https://github.com/inducer/pytools/workflows/CI/badge.svg?branch=master&event=push + :target: https://gitlab.tiker.net/inducer/pytools/commits/main +.. image:: https://github.com/inducer/pytools/workflows/CI/badge.svg?branch=main&event=push :alt: Github Build Status - :target: https://github.com/inducer/pytools/actions?query=branch%3Amaster+workflow%3ACI+event%3Apush + :target: https://github.com/inducer/pytools/actions?query=branch%3Amain+workflow%3ACI+event%3Apush .. image:: https://badge.fury.io/py/pytools.png :alt: Python Package Index Release Page :target: https://pypi.org/project/pytools/ @@ -19,7 +19,6 @@ * A ton of small tool functions such as `len_iterable`, `argmin`, tuple generation, permutation generation, ASCII table pretty printing, GvR's monkeypatch_xxx() hack, the elusive `flatten`, and much more. -* Michele Simionato's decorator module * A time-series logging module, `pytools.log`. * Batch job submission, `pytools.batchjob`. * A lexer, `pytools.lex`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/doc/reference.rst new/pytools-2021.2.3/doc/reference.rst --- old/pytools-2021.1/doc/reference.rst 2019-10-02 00:43:52.000000000 +0200 +++ new/pytools-2021.2.3/doc/reference.rst 2021-03-23 06:20:01.000000000 +0100 @@ -1 +1,2 @@ .. automodule:: pytools +.. automodule:: pytools.datatable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/__init__.py new/pytools-2021.2.3/pytools/__init__.py --- old/pytools-2021.1/pytools/__init__.py 2020-12-08 01:17:06.000000000 +0100 +++ new/pytools-2021.2.3/pytools/__init__.py 2021-04-03 00:55:56.000000000 +0200 @@ -25,6 +25,7 @@ """ +import re from functools import reduce, wraps import operator import sys @@ -37,9 +38,6 @@ from sys import intern -decorator_module = __import__("decorator", level=0) -my_decorator = decorator_module.decorator - # These are deprecated and will go away in 2022. all = builtins.all any = builtins.any @@ -73,6 +71,7 @@ .. autofunction:: memoize_in .. autofunction:: keyed_memoize_on_first_arg .. autofunction:: keyed_memoize_method +.. autofunction:: keyed_memoize_in Argmin/max ---------- @@ -163,6 +162,16 @@ .. autofunction:: natorder .. autofunction:: natsorted +Backports of newer Python functionality +--------------------------------------- + +.. autofunction:: resolve_name + +Hashing +------- + +.. autofunction:: unordered_hash + Type Variables Used ------------------- @@ -613,43 +622,48 @@ % ", ".join(list(kwargs.keys()))) if key_func is not None: - @my_decorator - def _deco(func, *args, **kwargs): - # by Michele Simionato - # http://www.phyast.pitt.edu/~micheles/python/ - key = key_func(*args, **kwargs) - try: - return func._memoize_dic[key] # pylint: disable=protected-access - except AttributeError: - # _memoize_dic doesn't exist yet. - result = func(*args, **kwargs) - func._memoize_dic = {key: result} # pylint: disable=protected-access - return result - except KeyError: - result = func(*args, **kwargs) - func._memoize_dic[key] = result # pylint: disable=protected-access - return result + def _decorator(func): + def wrapper(*args, **kwargs): + key = key_func(*args, **kwargs) + try: + return func._memoize_dic[key] # noqa: E501 # pylint: disable=protected-access + except AttributeError: + # _memoize_dic doesn't exist yet. + result = func(*args, **kwargs) + func._memoize_dic = {key: result} # noqa: E501 # pylint: disable=protected-access + return result + except KeyError: + result = func(*args, **kwargs) + func._memoize_dic[key] = result # noqa: E501 # pylint: disable=protected-access + return result + + from functools import update_wrapper + update_wrapper(wrapper, func) + return wrapper + else: - @my_decorator - def _deco(func, *args): - # by Michele Simionato - # http://www.phyast.pitt.edu/~micheles/python/ - try: - return func._memoize_dic[args] # pylint: disable=protected-access - except AttributeError: - # _memoize_dic doesn't exist yet. - result = func(*args) - func._memoize_dic = {args: result} # pylint:disable=protected-access - return result - except KeyError: - result = func(*args) - func._memoize_dic[args] = result # pylint: disable=protected-access - return result + def _decorator(func): + def wrapper(*args): + try: + return func._memoize_dic[args] # noqa: E501 # pylint: disable=protected-access + except AttributeError: + # _memoize_dic doesn't exist yet. + result = func(*args) + func._memoize_dic = {args: result} # noqa: E501 # pylint:disable=protected-access + return result + except KeyError: + result = func(*args) + func._memoize_dic[args] = result # noqa: E501 # pylint: disable=protected-access + return result + + from functools import update_wrapper + update_wrapper(wrapper, func) + return wrapper if not args: - return _deco + return _decorator # type: ignore if callable(args[0]) and len(args) == 1: - return _deco(args[0]) + return _decorator(args[0]) raise TypeError( "memoize received unexpected position arguments: %s" % args) @@ -669,8 +683,9 @@ """ if cache_dict_name is None: - cache_dict_name = intern("_memoize_dic_" - + function.__module__ + function.__name__) + cache_dict_name = intern( + f"_memoize_dic_{function.__module__}{function.__name__}" + ) def wrapper(obj, *args, **kwargs): if kwargs: @@ -681,16 +696,20 @@ try: return getattr(obj, cache_dict_name)[key] except AttributeError: - result = function(obj, *args, **kwargs) - setattr(obj, cache_dict_name, {key: result}) - return result + attribute_error = True except KeyError: - result = function(obj, *args, **kwargs) + attribute_error = False + + result = function(obj, *args, **kwargs) + if attribute_error: + object.__setattr__(obj, cache_dict_name, {key: result}) + return result + else: getattr(obj, cache_dict_name)[key] = result return result def clear_cache(obj): - delattr(obj, cache_dict_name) + object.__delattr__(obj, cache_dict_name) from functools import update_wrapper new_wrapper = update_wrapper(wrapper, function) @@ -701,9 +720,15 @@ def memoize_method(method: F) -> F: """Supports cache deletion via ``method_name.clear_cache(self)``. + + .. versionchanged:: 2021.2 + + Can memoize methods on classes that do not allow setting attributes + (e.g. by overwritting ``__setattr__``), e.g. frozen :mod:`dataclasses`. """ - return memoize_on_first_arg(method, intern("_memoize_dic_"+method.__name__)) + return memoize_on_first_arg(method, + intern(f"_memoize_dic_{method.__name__}")) class keyed_memoize_on_first_arg: # noqa: N801 @@ -714,6 +739,8 @@ :arg key: A function receiving the same arguments as the decorated function which computes and returns the cache key. + :arg cache_dict_name: The name of the `dict` attribute in the instance + used to hold the cache. .. versionadded :: 2020.3 """ @@ -723,8 +750,7 @@ self.cache_dict_name = cache_dict_name def _default_cache_dict_name(self, function): - return intern("_memoize_dic_" - + function.__module__ + function.__name__) + return intern(f"_memoize_dic_{function.__module__}{function.__name__}") def __call__(self, function): cache_dict_name = self.cache_dict_name @@ -740,7 +766,7 @@ return getattr(obj, cache_dict_name)[cache_key] except AttributeError: result = function(obj, *args, **kwargs) - setattr(obj, cache_dict_name, {cache_key: result}) + object.__setattr__(obj, cache_dict_name, {cache_key: result}) return result except KeyError: result = function(obj, *args, **kwargs) @@ -748,7 +774,7 @@ return result def clear_cache(obj): - delattr(obj, cache_dict_name) + object.__delattr__(obj, cache_dict_name) from functools import update_wrapper new_wrapper = update_wrapper(wrapper, function) @@ -758,15 +784,23 @@ class keyed_memoize_method(keyed_memoize_on_first_arg): # noqa: N801 - """Supports cache deletion via ``method_name.clear_cache(self)``. + """Like :class:`memoize_method`, but additionally uses a function *key* to + compute the key under which the function result is stored. + + Supports cache deletion via ``method_name.clear_cache(self)``. :arg key: A function receiving the same arguments as the decorated function which computes and returns the cache key. .. versionadded :: 2020.3 + + .. versionchanged:: 2021.2 + + Can memoize methods on classes that do not allow setting attributes + (e.g. by overwritting ``__setattr__``), e.g. frozen :mod:`dataclasses`. """ def _default_cache_dict_name(self, function): - return intern("_memoize_dic_" + function.__name__) + return intern(f"_memoize_dic_{function.__name__}") def memoize_method_with_uncached(uncached_args=None, uncached_kwargs=None): @@ -791,7 +825,7 @@ uncached_kwargs = list(uncached_kwargs) def parametrized_decorator(method): - cache_dict_name = intern("_memoize_dic_"+method.__name__) + cache_dict_name = intern(f"_memoize_dic_{method.__name__}") def wrapper(self, *args, **kwargs): cache_args = list(args) @@ -817,7 +851,7 @@ return getattr(self, cache_dict_name)[key] except AttributeError: result = method(self, *args, **kwargs) - setattr(self, cache_dict_name, {key: result}) + object.__setattr__(self, cache_dict_name, {key: result}) return result except KeyError: result = method(self, *args, **kwargs) @@ -825,7 +859,7 @@ return result def clear_cache(self): - delattr(self, cache_dict_name) + object.__delattr__(self, cache_dict_name) if sys.version_info >= (2, 5): from functools import update_wrapper @@ -837,55 +871,28 @@ return parametrized_decorator -def memoize_method_nested(inner): - """Adds a cache to a function nested inside a method. The cache is attached - to *memoize_cache_context* (if it exists) or *self* in the outer (method) - namespace. - - Requires Python 2.5 or newer. - """ - - from warnings import warn - warn("memoize_method_nested is deprecated and will go away in 2021. " - "Use @memoize_in(self, 'identifier') instead", DeprecationWarning, - stacklevel=2) - - cache_dict_name = intern("_memoize_inner_dic_%s_%s_%d" - % (inner.__name__, inner.__code__.co_filename, - inner.__code__.co_firstlineno)) - - from inspect import currentframe - outer_frame = currentframe().f_back - cache_context = outer_frame.f_locals.get("memoize_cache_context") - if cache_context is None: - cache_context = outer_frame.f_locals.get("self") - - try: - cache_dict = getattr(cache_context, cache_dict_name) - except AttributeError: - cache_dict = {} - setattr(cache_context, cache_dict_name, cache_dict) - - @wraps(inner) - def new_inner(*args): - try: - return cache_dict[args] - except KeyError: - result = inner(*args) - cache_dict[args] = result - return result - - return new_inner +class memoize_in: # noqa + """Adds a cache to the function it decorates. The cache is attached + to *container* and must be uniquely specified by *identifier* (i.e. + all functions using the same *container* and *identifier* will be using + the same cache). The decorated function may only receive positional + arguments. + .. note:: -class memoize_in: # noqa - """Adds a cache to a function nested inside a method. The cache is attached - to *container*. + This function works well on nested functions, which + do not have stable global identifiers. .. versionchanged :: 2020.3 *identifier* no longer needs to be a :class:`str`, but it needs to be hashable. + + .. versionchanged:: 2021.2.1 + + Can now use instances of classes as *container* that do not allow + setting attributes (e.g. by overwritting ``__setattr__``), + e.g. frozen :mod:`dataclasses`. """ def __init__(self, container, identifier): @@ -893,7 +900,8 @@ memoize_in_dict = container._pytools_memoize_in_dict except AttributeError: memoize_in_dict = {} - container._pytools_memoize_in_dict = memoize_in_dict + object.__setattr__(container, "_pytools_memoize_in_dict", + memoize_in_dict) self.cache_dict = memoize_in_dict.setdefault(identifier, {}) @@ -909,6 +917,41 @@ return new_inner + +class keyed_memoize_in: # noqa + """Like :class:`memoize_in`, but additionally uses a function *key* to + compute the key under which the function result is memoized. + + :arg key: A function receiving the same arguments as the decorated function + which computes and returns the cache key. + + .. versionadded :: 2021.2.1 + """ + + def __init__(self, container, identifier, key): + try: + memoize_in_dict = container._pytools_keyed_memoize_in_dict + except AttributeError: + memoize_in_dict = {} + object.__setattr__(container, "_pytools_keyed_memoize_in_dict", + memoize_in_dict) + + self.cache_dict = memoize_in_dict.setdefault(identifier, {}) + self.key = key + + def __call__(self, inner): + @wraps(inner) + def new_inner(*args): + key = self.key(*args) + try: + return self.cache_dict[key] + except KeyError: + result = inner(*args) + self.cache_dict[key] = result + return result + + return new_inner + # }}} @@ -2397,7 +2440,11 @@ # Can happen, e.g., if pudb is controlling the console. use_late_start_logging = False else: - use_late_start_logging = sys.stdin.isatty() + if hasattr(sys.stdin, "closed") and not sys.stdin.closed: + # can query stdin.isatty() only if stdin's open + use_late_start_logging = sys.stdin.isatty() + else: + use_late_start_logging = False import os if os.environ.get("PYTOOLS_LOG_NO_THREADS", ""): @@ -2542,6 +2589,107 @@ # }}} + +# {{{ resolve_name + +# https://github.com/python/cpython/commit/1ed61617a4a6632905ad6a0b440cd2cafb8b6414 + +_DOTTED_WORDS = r"[a-z_]\w*(\.[a-z_]\w*)*" +_NAME_PATTERN = re.compile(f"^({_DOTTED_WORDS})(:({_DOTTED_WORDS})?)?$", re.I) +del _DOTTED_WORDS + + +def resolve_name(name): + """A backport of :func:`pkgutil.resolve_name` (added in Python 3.9). + + .. versionadded:: 2021.1.2 + """ + # Delete the tail of the function and deprecate this once we require Python 3.9. + if sys.version_info >= (3, 9): + # use the official version + import pkgutil + return pkgutil.resolve_name(name) # pylint: disable=no-member + + import importlib + + m = _NAME_PATTERN.match(name) + if not m: + raise ValueError(f"invalid format: {name!r}") + groups = m.groups() + if groups[2]: + # there is a colon - a one-step import is all that's needed + mod = importlib.import_module(groups[0]) + parts = groups[3].split(".") if groups[3] else [] + else: + # no colon - have to iterate to find the package boundary + parts = name.split(".") + modname = parts.pop(0) + # first part *must* be a module/package. + mod = importlib.import_module(modname) + while parts: + p = parts[0] + s = f"{modname}.{p}" + try: + mod = importlib.import_module(s) + parts.pop(0) + modname = s + except ImportError: + break + # if we reach this point, mod is the module, already imported, and + # parts is the list of parts in the object hierarchy to be traversed, or + # an empty list if just the module is wanted. + result = mod + for p in parts: + result = getattr(result, p) + return result + +# }}} + + +# {{{ unordered_hash + +def unordered_hash(hash_instance, iterable, hash_constructor=None): + """Using a hash algorithm given by the parameter-less constructor + *hash_constructor*, return a hash object whose internal state + depends on the entries of *iterable*, but not their order. If *hash* + is the instance returned by evaluating ``hash_constructor()``, then + the each entry *i* of the iterable must permit ``hash.upate(i)`` to + succeed. An example of *hash_constructor* is ``hashlib.sha256`` + from :mod:`hashlib`. ``hash.digest_size`` must also be defined. + If *hash_constructor* is not provided, ``hash_instance.name`` is + used to deduce it. + + :returns: the updated *hash_instance*. + + .. warning:: + + The construction used in this function is likely not cryptographically + secure. Do not use this function in a security-relevant context. + + .. versionadded:: 2021.2 + """ + + if hash_constructor is None: + from functools import partial + import hashlib + hash_constructor = partial(hashlib.new, hash_instance.name) + + h_int = 0 + for i in iterable: + h_i = hash_constructor() + h_i.update(i) + # Using sys.byteorder (for efficiency) here technically makes the + # hash system-dependent (which it should not be), however the + # effect of this is undone by the to_bytes conversion below, while + # left invariant by the intervening XOR operations (which do not + # mix adjacent bits). + h_int = h_int ^ int.from_bytes(h_i.digest(), sys.byteorder) + + hash_instance.update(h_int.to_bytes(hash_instance.digest_size, sys.byteorder)) + return hash_instance + +# }}} + def _test(): import doctest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/convergence.py new/pytools-2021.2.3/pytools/convergence.py --- old/pytools-2021.1/pytools/convergence.py 2020-12-07 21:54:53.000000000 +0100 +++ new/pytools-2021.2.3/pytools/convergence.py 2021-03-23 06:20:01.000000000 +0100 @@ -30,6 +30,11 @@ abscissae = np.array([a for a, e in self.history]) errors = np.array([e for a, e in self.history]) + # NOTE: in case any of the errors are exactly 0.0, which + # can give NaNs in `estimate_order_of_convergence` + emax = np.amax(errors) + errors += (1 if emax == 0 else emax) * np.finfo(errors.dtype).eps + size = len(abscissae) if gliding_mean is None: gliding_mean = size diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/datatable.py new/pytools-2021.2.3/pytools/datatable.py --- old/pytools-2021.1/pytools/datatable.py 2020-12-07 21:54:53.000000000 +0100 +++ new/pytools-2021.2.3/pytools/datatable.py 2021-03-23 06:20:01.000000000 +0100 @@ -1,19 +1,33 @@ from pytools import Record +__doc__ = """ +An in-memory relational database table +====================================== + +.. autoclass:: DataTable +""" + + class Row(Record): pass class DataTable: - """An in-memory relational database table.""" + """An in-memory relational database table. + + .. automethod:: __init__ + .. automethod:: copy + .. automethod:: deep_copy + .. automethod:: join + """ def __init__(self, column_names, column_data=None): """Construct a new table, with the given C{column_names}. - @arg column_names: An indexable of column name strings. - @arg column_data: None or a list of tuples of the same length as - C{column_names} indicating an initial set of data. + :arg column_names: An indexable of column name strings. + :arg column_data: None or a list of tuples of the same length as + *column_names* indicating an initial set of data. """ if column_data is None: self.data = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/decorator.py new/pytools-2021.2.3/pytools/decorator.py --- old/pytools-2021.1/pytools/decorator.py 2020-12-07 21:54:53.000000000 +0100 +++ new/pytools-2021.2.3/pytools/decorator.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,159 +0,0 @@ -# Python decorator module -# by Michele Simionato -# http://www.phyast.pitt.edu/~micheles/python/ - -## The basic trick is to generate the source code for the decorated function -## with the right signature and to evaluate it. -## Uncomment the statement 'print >> sys.stderr, func_src' in _decorate -## to understand what is going on. - -__all__ = ["decorator", "update_wrapper", "getinfo"] - -import inspect - -def getinfo(func): - """ - Returns an info dictionary containing: - - name (the name of the function : str) - - argnames (the names of the arguments : list) - - defaults (the values of the default arguments : tuple) - - signature (the signature : str) - - doc (the docstring : str) - - module (the module name : str) - - dict (the function __dict__ : str) - - >>> def f(self, x=1, y=2, *args, **kw): pass - - >>> info = getinfo(f) - - >>> info["name"] - 'f' - >>> info["argnames"] - ['self', 'x', 'y', 'args', 'kw'] - - >>> info["defaults"] - (1, 2) - - >>> info["signature"] - 'self, x, y, *args, **kw' - """ - assert inspect.ismethod(func) or inspect.isfunction(func) - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) - argnames = list(regargs) - if varargs: - argnames.append(varargs) - if varkwargs: - argnames.append(varkwargs) - signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: "")[1:-1] - return dict(name=func.__name__, argnames=argnames, signature=signature, - defaults = func.__defaults__, doc=func.__doc__, - module=func.__module__, dict=func.__dict__, - globals=func.__globals__, closure=func.__closure__) - -def update_wrapper(wrapper, wrapped, create=False): - """ - An improvement over functools.update_wrapper. By default it works the - same, but if the 'create' flag is set, generates a copy of the wrapper - with the right signature and update the copy, not the original. - Moreovoer, 'wrapped' can be a dictionary with keys 'name', 'doc', 'module', - 'dict', 'defaults'. - """ - if isinstance(wrapped, dict): - infodict = wrapped - else: # assume wrapped is a function - infodict = getinfo(wrapped) - assert not '_wrapper_' in infodict["argnames"], \ - '"_wrapper_" is a reserved argument name!' - if create: # create a brand new wrapper with the right signature - src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict - # import sys; print >> sys.stderr, src # for debugging purposes - wrapper = eval(src, dict(_wrapper_=wrapper)) - try: - wrapper.__name__ = infodict['name'] - except: # Python version < 2.4 - pass - wrapper.__doc__ = infodict['doc'] - wrapper.__module__ = infodict['module'] - wrapper.__dict__.update(infodict['dict']) - wrapper.__defaults__ = infodict['defaults'] - return wrapper - -# the real meat is here -def _decorator(caller, func): - if not (inspect.ismethod(func) or inspect.isfunction(func)): - # skip all the fanciness, just do what works - return lambda *args, **kwargs: caller(func, *args, **kwargs) - - infodict = getinfo(func) - argnames = infodict['argnames'] - assert not ('_call_' in argnames or '_func_' in argnames), \ - 'You cannot use _call_ or _func_ as argument names!' - src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict - dec_func = eval(src, dict(_func_=func, _call_=caller)) - return update_wrapper(dec_func, func) - -def decorator(caller, func=None): - """ - General purpose decorator factory: takes a caller function as - input and returns a decorator with the same attributes. - A caller function is any function like this:: - - def caller(func, *args, **kw): - # do something - return func(*args, **kw) - - Here is an example of usage: - - >>> @decorator - ... def chatty(f, *args, **kw): - ... print("Calling %r" % f.__name__) - ... return f(*args, **kw) - - >>> chatty.__name__ - 'chatty' - - >>> @chatty - ... def f(): pass - ... - >>> f() - Calling 'f' - - For sake of convenience, the decorator factory can also be called with - two arguments. In this casem ``decorator(caller, func)`` is just a - shortcut for ``decorator(caller)(func)``. - """ - from warnings import warn - warn("pytools.decorator is deprecated and will be removed in pytools 12. " - "Use the 'decorator' module directly instead.", - DeprecationWarning, stacklevel=2) - - if func is None: # return a decorator function - return update_wrapper(lambda f : _decorator(caller, f), caller) - else: # return a decorated function - return _decorator(caller, func) - -if __name__ == "__main__": - import doctest; doctest.testmod() - -####################### LEGALESE ################################## - -## Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## Redistributions in bytecode form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. - -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -## DAMAGE. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/obj_array.py new/pytools-2021.2.3/pytools/obj_array.py --- old/pytools-2021.1/pytools/obj_array.py 2020-12-07 21:54:53.000000000 +0100 +++ new/pytools-2021.2.3/pytools/obj_array.py 2021-04-05 20:35:59.000000000 +0200 @@ -21,8 +21,8 @@ """ import numpy as np +from functools import partial, update_wrapper from warnings import warn -from pytools import my_decorator as decorator __doc__ = """ @@ -142,7 +142,10 @@ return f(ary) -obj_array_vectorized = decorator(obj_array_vectorize) +def obj_array_vectorized(f): + wrapper = partial(obj_array_vectorize, f) + update_wrapper(wrapper, f) + return wrapper def rec_obj_array_vectorize(f, ary): @@ -168,7 +171,10 @@ return f(ary) -rec_obj_array_vectorized = decorator(rec_obj_array_vectorize) +def rec_obj_array_vectorized(f): + wrapper = partial(rec_obj_array_vectorize, f) + update_wrapper(wrapper, f) + return wrapper def obj_array_vectorize_n_args(f, *args): @@ -207,7 +213,25 @@ return result -obj_array_vectorized_n_args = decorator(obj_array_vectorize_n_args) +def obj_array_vectorized_n_args(f): + # Unfortunately, this can't use partial(), as the callable returned by it + # will not be turned into a bound method upon attribute access. + # This may happen here, because the decorator *could* be used + # on methods, since it can "look past" the leading `self` argument. + # Only exactly function objects receive this treatment. + # + # Spec link: + # https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy + # (under "Instance Methods", quote as of Py3.9.4) + # > Also notice that this transformation only happens for user-defined functions; + # > other callable objects (and all non-callable objects) are retrieved + # > without transformation. + + def wrapper(*args): + return obj_array_vectorize_n_args(f, *args) + + update_wrapper(wrapper, f) + return wrapper # {{{ workarounds for https://github.com/numpy/numpy/issues/1740 @@ -365,7 +389,10 @@ return f(field) -as_oarray_func = decorator(with_object_array_or_scalar) +def as_oarray_func(f): + wrapper = partial(with_object_array_or_scalar, f) + update_wrapper(wrapper, f) + return wrapper def with_object_array_or_scalar_n_args(f, *args): @@ -398,7 +425,10 @@ return f(*args) -as_oarray_func_n_args = decorator(with_object_array_or_scalar_n_args) +def as_oarray_func_n_args(f): + wrapper = partial(with_object_array_or_scalar_n_args, f) + update_wrapper(wrapper, f) + return wrapper def oarray_real(ary): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/persistent_dict.py new/pytools-2021.2.3/pytools/persistent_dict.py --- old/pytools-2021.1/pytools/persistent_dict.py 2020-12-22 22:32:38.000000000 +0100 +++ new/pytools-2021.2.3/pytools/persistent_dict.py 2021-03-23 06:20:01.000000000 +0100 @@ -173,7 +173,37 @@ # {{{ key generation class KeyBuilder: + """A (stateless) object that computes hashes of objects fed to it. Subclassing + this class permits customizing the computation of hash keys. + + .. automethod:: __call__ + .. automethod:: rec + .. staticmethod:: new_hash() + + Return a new hash instance following the protocol of the ones + from :mod:`hashlib`. This will permit switching to different + hash algorithms in the future. Subclasses are expected to use + this to create new hashes. Not doing so is deprecated and + may stop working as early as 2022. + + .. versionadded:: 2021.2 + """ + + # this exists so that we can (conceivably) switch algorithms at some point + # down the road + new_hash = hashlib.sha256 + def rec(self, key_hash, key): + """ + :arg key_hash: the hash object to be updated with the hash of *key*. + :arg key: the (immutable) Python object to be hashed. + :returns: the updated *key_hash* + + .. versionchanged:: 2021.2 + + Now returns the updated *key_hash*. + """ + digest = None try: @@ -187,7 +217,7 @@ except AttributeError: pass else: - inner_key_hash = hashlib.sha256() + inner_key_hash = self.new_hash() method(inner_key_hash, self) digest = inner_key_hash.digest() @@ -205,7 +235,7 @@ method = self.update_for_specific_dtype if method is not None: - inner_key_hash = hashlib.sha256() + inner_key_hash = self.new_hash() method(inner_key_hash, key) digest = inner_key_hash.digest() @@ -222,9 +252,10 @@ pass key_hash.update(digest) + return key_hash def __call__(self, key): - key_hash = hashlib.sha256() + key_hash = self.new_hash() self.rec(key_hash, key) return key_hash.hexdigest() @@ -232,14 +263,21 @@ @staticmethod def update_for_int(key_hash, key): - key_hash.update(str(key).encode("utf8")) + sz = 8 + while True: + try: + key_hash.update(key.to_bytes(sz, byteorder="little", signed=True)) + return + except OverflowError: + sz *= 2 - update_for_long = update_for_int - update_for_bool = update_for_int + @staticmethod + def update_for_bool(key_hash, key): + key_hash.update(str(key).encode("utf8")) @staticmethod def update_for_float(key_hash, key): - key_hash.update(repr(key).encode("utf8")) + key_hash.update(key.hex().encode("utf8")) @staticmethod def update_for_str(key_hash, key): @@ -254,8 +292,11 @@ self.rec(key_hash, obj_i) def update_for_frozenset(self, key_hash, key): - for set_key in sorted(key): - self.rec(key_hash, set_key) + from pytools import unordered_hash + + unordered_hash( + key_hash, + (self.rec(self.new_hash(), key_i).digest() for key_i in key)) @staticmethod def update_for_NoneType(key_hash, key): # noqa @@ -426,7 +467,7 @@ import appdirs container_dir = join( appdirs.user_cache_dir("pytools", "pytools"), - "pdict-v3-{}-py{}".format( + "pdict-v4-{}-py{}".format( identifier, ".".join(str(i) for i in sys.version_info))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/tag.py new/pytools-2021.2.3/pytools/tag.py --- old/pytools-2021.1/pytools/tag.py 2021-01-09 01:08:43.000000000 +0100 +++ new/pytools-2021.2.3/pytools/tag.py 2021-04-02 23:51:53.000000000 +0200 @@ -122,6 +122,17 @@ def tag_name(self) -> DottedName: return DottedName.from_class(type(self)) + def update_persistent_hash(self, key_hash, key_builder): + key_builder.rec(key_hash, self.__class__.__qualname__) + + from dataclasses import fields + # Fields are ordered consistently, so ordered hashing is OK. + # + # No need to dispatch to superclass: fields() automatically gives us + # fields from the entire class hierarchy. + for f in fields(self): + key_builder.rec(key_hash, getattr(self, f.name)) + # }}} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools/version.py new/pytools-2021.2.3/pytools/version.py --- old/pytools-2021.1/pytools/version.py 2021-01-09 01:08:43.000000000 +0100 +++ new/pytools-2021.2.3/pytools/version.py 2021-04-05 20:36:13.000000000 +0200 @@ -1,3 +1,3 @@ -VERSION = (2021, 1) +VERSION = (2021, 2, 3) VERSION_STATUS = "" VERSION_TEXT = ".".join(str(x) for x in VERSION) + VERSION_STATUS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools.egg-info/PKG-INFO new/pytools-2021.2.3/pytools.egg-info/PKG-INFO --- old/pytools-2021.1/pytools.egg-info/PKG-INFO 2021-01-09 20:56:50.000000000 +0100 +++ new/pytools-2021.2.3/pytools.egg-info/PKG-INFO 2021-04-05 20:37:03.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: pytools -Version: 2021.1 +Version: 2021.2.3 Summary: A collection of tools for Python Home-page: http://pypi.python.org/pypi/pytools Author: Andreas Kloeckner @@ -9,12 +9,12 @@ Description: Pytools: Lots of Little Utilities ================================= - .. image:: https://gitlab.tiker.net/inducer/pytools/badges/master/pipeline.svg + .. image:: https://gitlab.tiker.net/inducer/pytools/badges/main/pipeline.svg :alt: Gitlab Build Status - :target: https://gitlab.tiker.net/inducer/pytools/commits/master - .. image:: https://github.com/inducer/pytools/workflows/CI/badge.svg?branch=master&event=push + :target: https://gitlab.tiker.net/inducer/pytools/commits/main + .. image:: https://github.com/inducer/pytools/workflows/CI/badge.svg?branch=main&event=push :alt: Github Build Status - :target: https://github.com/inducer/pytools/actions?query=branch%3Amaster+workflow%3ACI+event%3Apush + :target: https://github.com/inducer/pytools/actions?query=branch%3Amain+workflow%3ACI+event%3Apush .. image:: https://badge.fury.io/py/pytools.png :alt: Python Package Index Release Page :target: https://pypi.org/project/pytools/ @@ -27,7 +27,6 @@ * A ton of small tool functions such as `len_iterable`, `argmin`, tuple generation, permutation generation, ASCII table pretty printing, GvR's monkeypatch_xxx() hack, the elusive `flatten`, and much more. - * Michele Simionato's decorator module * A time-series logging module, `pytools.log`. * Batch job submission, `pytools.batchjob`. * A lexer, `pytools.lex`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools.egg-info/SOURCES.txt new/pytools-2021.2.3/pytools.egg-info/SOURCES.txt --- old/pytools-2021.1/pytools.egg-info/SOURCES.txt 2021-01-09 20:56:50.000000000 +0100 +++ new/pytools-2021.2.3/pytools.egg-info/SOURCES.txt 2021-04-05 20:37:03.000000000 +0200 @@ -19,7 +19,6 @@ pytools/convergence.py pytools/datatable.py pytools/debug.py -pytools/decorator.py pytools/graph.py pytools/lex.py pytools/log.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/pytools.egg-info/requires.txt new/pytools-2021.2.3/pytools.egg-info/requires.txt --- old/pytools-2021.1/pytools.egg-info/requires.txt 2021-01-09 20:56:50.000000000 +0100 +++ new/pytools-2021.2.3/pytools.egg-info/requires.txt 2021-04-05 20:37:03.000000000 +0200 @@ -1,5 +1,4 @@ appdirs>=1.4.0 -decorator>=3.2.0 numpy>=1.6.0 [:python_version <= "3.6"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/setup.cfg new/pytools-2021.2.3/setup.cfg --- old/pytools-2021.1/setup.cfg 2021-01-09 20:56:50.842988300 +0100 +++ new/pytools-2021.2.3/setup.cfg 2021-04-05 20:37:03.446051100 +0200 @@ -1,7 +1,6 @@ [flake8] ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503,E731 max-line-length = 85 -exclude = pytools/arithmetic_container.py,pytools/decorator.py inline-quotes = " docstring-quotes = " multiline-quotes = """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/setup.py new/pytools-2021.2.3/setup.py --- old/pytools-2021.1/setup.py 2020-12-07 21:54:53.000000000 +0100 +++ new/pytools-2021.2.3/setup.py 2021-04-03 00:55:56.000000000 +0200 @@ -38,7 +38,6 @@ python_requires="~=3.6", install_requires=[ - "decorator>=3.2.0", "appdirs>=1.4.0", "numpy>=1.6.0", "dataclasses>=0.7;python_version<='3.6'" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/test/test_persistent_dict.py new/pytools-2021.2.3/test/test_persistent_dict.py --- old/pytools-2021.1/test/test_persistent_dict.py 2020-12-07 21:54:53.000000000 +0100 +++ new/pytools-2021.2.3/test/test_persistent_dict.py 2021-03-23 06:20:01.000000000 +0100 @@ -4,6 +4,7 @@ import pytest +from pytools.tag import Tag, tag_dataclass from pytools.persistent_dict import (CollisionWarning, NoSuchEntryError, PersistentDict, ReadOnlyEntryError, WriteOncePersistentDict) @@ -39,6 +40,11 @@ # }}} +@tag_dataclass +class SomeTag(Tag): + value: str + + def test_persistent_dict_storage_and_lookup(): try: tmpdir = tempfile.mkdtemp() @@ -51,7 +57,10 @@ chr(65+randrange(26)) for i in range(n)) - keys = [(randrange(2000), rand_str(), None) for i in range(20)] + keys = [ + (randrange(2000)-1000, rand_str(), None, SomeTag(rand_str()), + frozenset({"abc", 123})) + for i in range(20)] values = [randrange(2000) for i in range(20)] d = dict(list(zip(keys, values))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytools-2021.1/test/test_pytools.py new/pytools-2021.2.3/test/test_pytools.py --- old/pytools-2021.1/test/test_pytools.py 2021-01-09 01:08:43.000000000 +0100 +++ new/pytools-2021.2.3/test/test_pytools.py 2021-04-05 20:35:59.000000000 +0200 @@ -1,3 +1,26 @@ +__copyright__ = "Copyright (C) 2009-2021 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + import sys import pytest @@ -49,8 +72,8 @@ sc.f.clear_cache(sc) # pylint: disable=no-member -def test_memoize_method_nested(): - from pytools import memoize_method_nested +def test_memoize_in(): + from pytools import memoize_in class SomeClass: def __init__(self): @@ -58,7 +81,7 @@ def f(self): - @memoize_method_nested + @memoize_in(self, (SomeClass.f,)) def inner(x): self.run_count += 1 return 2*x @@ -97,6 +120,20 @@ from pytools import memoize count = [0] + @memoize + def f(i, j): + count[0] += 1 + return i + j + + assert f(1, 2) == 3 + assert f(1, 2) == 3 + assert count[0] == 1 + + +def test_memoize_with_kwargs(): + from pytools import memoize + count = [0] + @memoize(use_kwargs=True) def f(i, j=1): count[0] += 1 @@ -131,6 +168,48 @@ assert count[0] == 2 +def test_memoize_frozen(): + from dataclasses import dataclass + from pytools import memoize_method + + # {{{ check frozen dataclass + + @dataclass(frozen=True) + class FrozenDataclass: + value: int + + @memoize_method + def double_value(self): + return 2 * self.value + + c = FrozenDataclass(10) + assert c.double_value() == 20 + c.double_value.clear_cache(c) # pylint: disable=no-member + + # }}} + + # {{{ check class with no setattr + + class FrozenClass: + value: int + + def __init__(self, value): + object.__setattr__(self, "value", value) + + def __setattr__(self, key, value): + raise AttributeError(f"cannot set attribute {key}") + + @memoize_method + def double_value(self): + return 2 * self.value + + c = FrozenClass(10) + assert c.double_value() == 20 + c.double_value.clear_cache(c) # pylint: disable=no-member + + # }}} + + @pytest.mark.parametrize("dims", [2, 3]) def test_spatial_btree(dims, do_plot=False): pytest.importorskip("numpy") @@ -283,6 +362,86 @@ # }}} +# {{{ test obj array vectorization and decorators + +def test_obj_array_vectorize(c=1): + np = pytest.importorskip("numpy") + la = pytest.importorskip("numpy.linalg") + + # {{{ functions + + import pytools.obj_array as obj + + def add_one(ary): + assert ary.dtype.char != "O" + return ary + c + + def two_add_one(x, y): + assert x.dtype.char != "O" and y.dtype.char != "O" + return x * y + c + + @obj.obj_array_vectorized + def vectorized_add_one(ary): + assert ary.dtype.char != "O" + return ary + c + + @obj.obj_array_vectorized_n_args + def vectorized_two_add_one(x, y): + assert x.dtype.char != "O" and y.dtype.char != "O" + return x * y + c + + class Adder: + def __init__(self, c): + self.c = c + + def add(self, ary): + assert ary.dtype.char != "O" + return ary + self.c + + @obj.obj_array_vectorized_n_args + def vectorized_add(self, ary): + assert ary.dtype.char != "O" + return ary + self.c + + adder = Adder(c) + + # }}} + + # {{{ check + + scalar_ary = np.ones(42, dtype=np.float) + object_ary = obj.make_obj_array([scalar_ary, scalar_ary, scalar_ary]) + + for func, vectorizer, nargs in [ + (add_one, obj.obj_array_vectorize, 1), + (two_add_one, obj.obj_array_vectorize_n_args, 2), + (adder.add, obj.obj_array_vectorize, 1), + ]: + input_ary = [scalar_ary] * nargs + result = vectorizer(func, *input_ary) + error = la.norm(result - c - 1) + print(error) + + input_ary = [object_ary] * nargs + result = vectorizer(func, *input_ary) + error = 0 + + for func, nargs in [ + (vectorized_add_one, 1), + (vectorized_two_add_one, 2), + (adder.vectorized_add, 1), + ]: + input_ary = [scalar_ary] * nargs + result = func(*input_ary) + + input_ary = [object_ary] * nargs + result = func(*input_ary) + + # }}} + +# }}} + + def test_tag(): from pytools.tag import Taggable, Tag, UniqueTag, NonUniqueTagError @@ -370,6 +529,28 @@ t4.without_tags(red_ribbon) +def test_unordered_hash(): + import random + import hashlib + + # FIXME: Use randbytes once >=3.9 is OK + lst = [bytes([random.randrange(256) for _ in range(20)]) + for _ in range(200)] + lorig = lst[:] + random.shuffle(lst) + + from pytools import unordered_hash + assert (unordered_hash(hashlib.sha256(), lorig).digest() + == unordered_hash(hashlib.sha256(), lst).digest()) + assert (unordered_hash(hashlib.sha256(), lorig).digest() + == unordered_hash(hashlib.sha256(), lorig).digest()) + assert (unordered_hash(hashlib.sha256(), lorig).digest() + != unordered_hash(hashlib.sha256(), lorig[:-1]).digest()) + lst[0] = b"aksdjfla;sdfjafd" + assert (unordered_hash(hashlib.sha256(), lorig).digest() + != unordered_hash(hashlib.sha256(), lst).digest()) + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1])