cdxcore 0.1.28__tar.gz → 0.1.29__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cdxcore might be problematic. Click here for more details.
- {cdxcore-0.1.28 → cdxcore-0.1.29}/PKG-INFO +1 -1
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/__init__.py +1 -1
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/subdir.py +213 -100
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/uniquehash.py +8 -9
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/version.py +7 -1
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore.egg-info/PKG-INFO +1 -1
- {cdxcore-0.1.28 → cdxcore-0.1.29}/pyproject.toml +1 -1
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_subdir.py +83 -2
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_uniquehash.py +9 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/LICENSE +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/README.md +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/config.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/deferred.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/dynalimits.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/dynaplot.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/err.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/filelock.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/jcpool.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/npio.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/npshm.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/pretty.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/util.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore/verbose.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore.egg-info/SOURCES.txt +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore.egg-info/dependency_links.txt +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore.egg-info/requires.txt +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/cdxcore.egg-info/top_level.txt +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/docs/source/conf.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/setup.cfg +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_config.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_deferred.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_err.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_jcpool.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_npio.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_npshm.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_pretty.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_util.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_verbose.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tests/test_version.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tmp/filelock.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tmp/np.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tmp/npsh1.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/tmp/sharedarray.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/up/git_message.py +0 -0
- {cdxcore-0.1.28 → cdxcore-0.1.29}/up/pip_modify_setup.py +0 -0
|
@@ -366,6 +366,7 @@ from collections import OrderedDict
|
|
|
366
366
|
from collections.abc import Collection, Mapping, Callable, Iterable
|
|
367
367
|
from enum import Enum
|
|
368
368
|
from functools import update_wrapper
|
|
369
|
+
from string import Formatter
|
|
369
370
|
|
|
370
371
|
import json as json
|
|
371
372
|
import gzip as gzip
|
|
@@ -375,7 +376,7 @@ from .err import verify, error, warn, fmt as txtfmt
|
|
|
375
376
|
from .pretty import PrettyObject
|
|
376
377
|
from .verbose import Context
|
|
377
378
|
from .version import Version, version as version_decorator, VersionError
|
|
378
|
-
from .util import fmt_list, fmt_filename, DEF_FILE_NAME_MAP, plain, is_filename
|
|
379
|
+
from .util import fmt_list, fmt_filename, DEF_FILE_NAME_MAP, plain, is_filename, fmt_dict
|
|
379
380
|
from .uniquehash import unique_hash48, UniqueLabel, NamedUniqueHash, named_unique_filename48_8
|
|
380
381
|
|
|
381
382
|
"""
|
|
@@ -1031,8 +1032,9 @@ class SubDir(object):
|
|
|
1031
1032
|
"""
|
|
1032
1033
|
Delete all of the current directory if ``tclean`` is ``True``
|
|
1033
1034
|
"""
|
|
1034
|
-
if self
|
|
1035
|
+
if getattr(self, "_tclean", False):
|
|
1035
1036
|
self.delete_everything(keep_directory=False)
|
|
1037
|
+
self._path = None
|
|
1036
1038
|
|
|
1037
1039
|
@staticmethod
|
|
1038
1040
|
def expand_std_root( name ):
|
|
@@ -1078,7 +1080,9 @@ class SubDir(object):
|
|
|
1078
1080
|
|
|
1079
1081
|
def path_exists(self) -> bool:
|
|
1080
1082
|
""" Whether the current directory exists """
|
|
1081
|
-
|
|
1083
|
+
if self._path is None:
|
|
1084
|
+
return False
|
|
1085
|
+
return os.path.exists( self._path[:-1] )
|
|
1082
1086
|
|
|
1083
1087
|
# -- a few basic properties --
|
|
1084
1088
|
|
|
@@ -2193,8 +2197,11 @@ class SubDir(object):
|
|
|
2193
2197
|
----------
|
|
2194
2198
|
delete_self: bool
|
|
2195
2199
|
Whether to delete the directory itself as well, or only its contents.
|
|
2200
|
+
If ``True``, the current object will be left in ``None`` state.
|
|
2201
|
+
|
|
2196
2202
|
raise_on_error: bool
|
|
2197
2203
|
``False`` for silent failure
|
|
2204
|
+
|
|
2198
2205
|
ext : str | None, default ``None``
|
|
2199
2206
|
Extension for keys, or ``None`` for the directory's default.
|
|
2200
2207
|
Use ``""`` to match all files regardless of extension.
|
|
@@ -2228,8 +2235,9 @@ class SubDir(object):
|
|
|
2228
2235
|
Deletes the entire sub directory will all contents.
|
|
2229
2236
|
|
|
2230
2237
|
*WARNING:* deletes *all* files and sub-directories, not just those with the present extension.
|
|
2231
|
-
If ``keep_directory`` is ``False``, the directory referred to by this object will also be deleted.
|
|
2232
|
-
|
|
2238
|
+
If ``keep_directory`` is ``False``, then the directory referred to by this object will also be deleted.
|
|
2239
|
+
|
|
2240
|
+
In this case, ``self`` will be set to ``None`` state.
|
|
2233
2241
|
"""
|
|
2234
2242
|
if self._path is None:
|
|
2235
2243
|
return
|
|
@@ -2801,15 +2809,16 @@ class SubDir(object):
|
|
|
2801
2809
|
# caching
|
|
2802
2810
|
# -------
|
|
2803
2811
|
|
|
2804
|
-
def cache( self, version
|
|
2805
|
-
dependencies
|
|
2806
|
-
label
|
|
2807
|
-
uid
|
|
2808
|
-
name
|
|
2809
|
-
exclude_args
|
|
2810
|
-
include_args
|
|
2811
|
-
exclude_arg_types
|
|
2812
|
-
version_auto_class
|
|
2812
|
+
def cache( self, version : str|None = None , *,
|
|
2813
|
+
dependencies : list|None = None,
|
|
2814
|
+
label : Callable|None = None,
|
|
2815
|
+
uid : Callable|None = None,
|
|
2816
|
+
name : str|None = None,
|
|
2817
|
+
exclude_args : list[str]|None = None,
|
|
2818
|
+
include_args : list[str]|None = None,
|
|
2819
|
+
exclude_arg_types : list[type]|None = None,
|
|
2820
|
+
version_auto_class : bool = True,
|
|
2821
|
+
name_of_func_name_arg: str = "func_name"):
|
|
2813
2822
|
"""
|
|
2814
2823
|
Advanced versioned caching for callables.
|
|
2815
2824
|
|
|
@@ -3158,7 +3167,7 @@ class SubDir(object):
|
|
|
3158
3167
|
return self.x*y
|
|
3159
3168
|
|
|
3160
3169
|
a = A(x=1)
|
|
3161
|
-
f = cache.cache("0.1",
|
|
3170
|
+
f = cache.cache("0.1", uid=lambda self, y : f"a.f({y})")(a.f) # <- decorate bound 'f'.
|
|
3162
3171
|
r = c(y=2)
|
|
3163
3172
|
|
|
3164
3173
|
In this case the function ``f`` is bound to ``a``. The object is added as ``self`` to the function
|
|
@@ -3223,7 +3232,7 @@ class SubDir(object):
|
|
|
3223
3232
|
@cache.cache_class("0.1")
|
|
3224
3233
|
class A(object):
|
|
3225
3234
|
|
|
3226
|
-
@cache.cache_init(
|
|
3235
|
+
@cache.cache_init(uid=lambda x, debug: f"A.__init__(x={x})") # <-- 'self' is not passed to the lambda function; no need to add **_
|
|
3227
3236
|
def __init__(self, x, debug):
|
|
3228
3237
|
if debug:
|
|
3229
3238
|
print("__init__",x)
|
|
@@ -3296,7 +3305,8 @@ class SubDir(object):
|
|
|
3296
3305
|
See :dec:`cdxcore.version.version` for details on name lookup if strings are used.
|
|
3297
3306
|
|
|
3298
3307
|
label : str | Callable | None, default ``None``
|
|
3299
|
-
Specify a human-
|
|
3308
|
+
Specify a human-readable label for the function call given its parameters.
|
|
3309
|
+
|
|
3300
3310
|
This label is used to generate the cache file name, and is also printed in when tracing
|
|
3301
3311
|
hashing operations. Labels are not assumed to be unique, hence a unique hash of
|
|
3302
3312
|
the label and the parameters to this function will be appended to generate
|
|
@@ -3307,14 +3317,22 @@ class SubDir(object):
|
|
|
3307
3317
|
|
|
3308
3318
|
**Usage:**
|
|
3309
3319
|
|
|
3320
|
+
* If ``label`` is a ``Callable`` then ``label( func_name=name, **parameters )`` will be called
|
|
3321
|
+
to generate the actual label.
|
|
3322
|
+
|
|
3323
|
+
The parameter ``func_name`` refers to the qualified
|
|
3324
|
+
name of the function. Its value can be overwitten by ``name``, while the parameter name itself
|
|
3325
|
+
can be overwritten using ``name_of_func_name_arg``, see below.
|
|
3326
|
+
|
|
3310
3327
|
* If ``label`` is a plain string without ``{}`` formatting: use this string as-is.
|
|
3311
3328
|
|
|
3312
|
-
* If ``label`` is a string with ``{}`` formatting, then ``label.format(
|
|
3313
|
-
will be used to generate the actual label.
|
|
3329
|
+
* If ``label`` is a string with ``{}`` formatting, then ``label.format( func_name=name, **parameters )``
|
|
3330
|
+
will be used to generate the actual label.
|
|
3331
|
+
|
|
3332
|
+
The parameter ``func_name`` refers to the qualified
|
|
3333
|
+
name of the function. Its value can be overwitten by ``name``, while the parameter name itself
|
|
3334
|
+
can be overwritten using ``name_of_func_name_arg``, see below.
|
|
3314
3335
|
|
|
3315
|
-
* If ``label`` is a ``Callable`` then ``label( name=name, **parameters )`` will be called
|
|
3316
|
-
to generate the actual label.
|
|
3317
|
-
|
|
3318
3336
|
See above for examples.
|
|
3319
3337
|
|
|
3320
3338
|
``label`` cannot be used alongside ``uid``.
|
|
@@ -3329,7 +3347,7 @@ class SubDir(object):
|
|
|
3329
3347
|
|
|
3330
3348
|
name : str | None, default ``None``
|
|
3331
3349
|
Name of this function which is used either on its own if neither ``label`` not ``uid`` are used,
|
|
3332
|
-
or which passed as a parameter ``
|
|
3350
|
+
or which passed as a parameter ``func_name`` to either the callable or the
|
|
3333
3351
|
formatting operator. See above for more details.
|
|
3334
3352
|
|
|
3335
3353
|
If ``name`` is not specified it defaults to ``__qualname__`` expanded
|
|
@@ -3350,6 +3368,34 @@ class SubDir(object):
|
|
|
3350
3368
|
Whether to automaticallty add version dependencies on base classes or, for member functions, on containing
|
|
3351
3369
|
classes. This is the ``auto_class`` parameter for :dec:`cdxcore.version.version`.
|
|
3352
3370
|
|
|
3371
|
+
name_of_func_name_arg : str, default ``"func_name"``
|
|
3372
|
+
When formatting ``label`` or ``uid``, by default ``"func_name"`` is used to refer to the current
|
|
3373
|
+
function name. If there is already a parameter ``func_name`` for the function, an error will be raised.
|
|
3374
|
+
Use this flag to change the parameter name. Example::
|
|
3375
|
+
|
|
3376
|
+
.. code-block:: python
|
|
3377
|
+
|
|
3378
|
+
from cdxcore.subdir import SubDir
|
|
3379
|
+
cache = SubDir("?/temp")
|
|
3380
|
+
|
|
3381
|
+
@cache.cache("0.1")
|
|
3382
|
+
def f( func_name, x ):
|
|
3383
|
+
pass
|
|
3384
|
+
|
|
3385
|
+
f("test", 1)
|
|
3386
|
+
|
|
3387
|
+
Generates a :class:`RuntimeError` ``f@__main__: 'func_name' is a reserved keyword
|
|
3388
|
+
and used as formatting parameter
|
|
3389
|
+
name for the function name. Found it also in the function parameter list. Use 'name_of_name_arg' to change the internal parameter name used.``.
|
|
3390
|
+
|
|
3391
|
+
Instead, use:
|
|
3392
|
+
|
|
3393
|
+
.. code-block:: python
|
|
3394
|
+
|
|
3395
|
+
@cache.cache("0.1", label=lambda new_func_name, func_name, x : f"{new_func_name}(): {func_name} {x}", name_of_func_name_arg="new_func_name")
|
|
3396
|
+
def f( func_name, x ):
|
|
3397
|
+
pass
|
|
3398
|
+
|
|
3353
3399
|
Returns
|
|
3354
3400
|
-------
|
|
3355
3401
|
Decorated F: Callable
|
|
@@ -3421,7 +3467,8 @@ class SubDir(object):
|
|
|
3421
3467
|
exclude_args = exclude_args,
|
|
3422
3468
|
include_args = include_args,
|
|
3423
3469
|
exclude_arg_types = exclude_arg_types,
|
|
3424
|
-
version_auto_class = version_auto_class
|
|
3470
|
+
version_auto_class = version_auto_class,
|
|
3471
|
+
name_of_func_name_arg = name_of_func_name_arg)
|
|
3425
3472
|
|
|
3426
3473
|
def cache_class( self,
|
|
3427
3474
|
version : str = None , *,
|
|
@@ -3633,7 +3680,7 @@ def _ensure_has_version( F,
|
|
|
3633
3680
|
dependencies=dependencies,
|
|
3634
3681
|
auto_class=auto_class)(F)
|
|
3635
3682
|
|
|
3636
|
-
def _qualified_name( F, name ):
|
|
3683
|
+
def _qualified_name( F, name = None ):
|
|
3637
3684
|
"""
|
|
3638
3685
|
Return qualified name including module name, robustly
|
|
3639
3686
|
"""
|
|
@@ -3652,6 +3699,36 @@ def _qualified_name( F, name ):
|
|
|
3652
3699
|
warn( f"Cannot determine module name for '{name}' of {type(F)}" )
|
|
3653
3700
|
return name
|
|
3654
3701
|
|
|
3702
|
+
def _expected_str_fmt_args(fmt: str):
|
|
3703
|
+
"""
|
|
3704
|
+
Inspect a format string and report what arguments it expects.
|
|
3705
|
+
Returns:
|
|
3706
|
+
- auto_positional: count of automatic '{}' fields
|
|
3707
|
+
- positional_indices: explicit numeric field indices used (e.g., {0}, {2})
|
|
3708
|
+
- keywords: named fields used (e.g., {user}, {price})
|
|
3709
|
+
"""
|
|
3710
|
+
f = Formatter()
|
|
3711
|
+
pos = set()
|
|
3712
|
+
auto = 0
|
|
3713
|
+
kws = set()
|
|
3714
|
+
|
|
3715
|
+
for literal, field, spec, conv in f.parse(fmt):
|
|
3716
|
+
if field is None:
|
|
3717
|
+
continue
|
|
3718
|
+
# Keep only the first identifier before attribute/index access
|
|
3719
|
+
head = field.split('.')[0].split('[')[0]
|
|
3720
|
+
if head == "": # '{}' → automatic positional
|
|
3721
|
+
auto += 1
|
|
3722
|
+
elif head.isdigit(): # '{0}', '{2}' → explicit positional
|
|
3723
|
+
pos.add(int(head))
|
|
3724
|
+
else: # '{name}' → keyword
|
|
3725
|
+
kws.add(head)
|
|
3726
|
+
|
|
3727
|
+
return PrettyObject( positional=auto,
|
|
3728
|
+
posindices=pos,
|
|
3729
|
+
keywords=kws
|
|
3730
|
+
)
|
|
3731
|
+
|
|
3655
3732
|
class CacheCallable(object):
|
|
3656
3733
|
"""
|
|
3657
3734
|
Wrapper for a cached function.
|
|
@@ -3660,17 +3737,17 @@ class CacheCallable(object):
|
|
|
3660
3737
|
"""
|
|
3661
3738
|
|
|
3662
3739
|
def __init__(self,
|
|
3663
|
-
subdir
|
|
3664
|
-
version
|
|
3665
|
-
dependencies
|
|
3666
|
-
label
|
|
3667
|
-
uid
|
|
3668
|
-
name
|
|
3669
|
-
exclude_args
|
|
3670
|
-
include_args
|
|
3671
|
-
exclude_arg_types
|
|
3672
|
-
version_auto_class
|
|
3673
|
-
|
|
3740
|
+
subdir : SubDir, *,
|
|
3741
|
+
version : str = None,
|
|
3742
|
+
dependencies : list,
|
|
3743
|
+
label : Callable = None,
|
|
3744
|
+
uid : Callable = None,
|
|
3745
|
+
name : str = None,
|
|
3746
|
+
exclude_args : set[str] = None,
|
|
3747
|
+
include_args : set[str] = None,
|
|
3748
|
+
exclude_arg_types : set[type] = None,
|
|
3749
|
+
version_auto_class : bool = True,
|
|
3750
|
+
name_of_func_name_arg: str = "name"):
|
|
3674
3751
|
"""
|
|
3675
3752
|
Utility class for :dec:`cdxcore.subdir.SubDir.cache`.
|
|
3676
3753
|
|
|
@@ -3679,18 +3756,38 @@ class CacheCallable(object):
|
|
|
3679
3756
|
if not label is None and not uid is None:
|
|
3680
3757
|
error("Cannot specify both 'label' and 'uid'.")
|
|
3681
3758
|
|
|
3682
|
-
self._subdir
|
|
3683
|
-
self.
|
|
3684
|
-
self._dependencies
|
|
3685
|
-
self._label
|
|
3686
|
-
self._uid
|
|
3687
|
-
self._name
|
|
3688
|
-
self._exclude_args
|
|
3689
|
-
self._include_args
|
|
3690
|
-
self._exclude_arg_types
|
|
3691
|
-
self._version_auto_class
|
|
3692
|
-
self.
|
|
3759
|
+
self._subdir = SubDir(subdir)
|
|
3760
|
+
self._input_version = str(version) if not version is None else None
|
|
3761
|
+
self._dependencies = list(dependencies) if not dependencies is None else None
|
|
3762
|
+
self._label = label
|
|
3763
|
+
self._uid = uid
|
|
3764
|
+
self._name = str(name) if not name is None else None
|
|
3765
|
+
self._exclude_args = set(exclude_args) if not exclude_args is None and len(exclude_args) > 0 else None
|
|
3766
|
+
self._include_args = set(include_args) if not include_args is None and len(include_args) > 0 else None
|
|
3767
|
+
self._exclude_arg_types = set(exclude_arg_types) if not exclude_arg_types is None and len(exclude_arg_types) > 0 else None
|
|
3768
|
+
self._version_auto_class = bool(version_auto_class)
|
|
3769
|
+
self._name_of_func_name_arg = str(name_of_func_name_arg)
|
|
3770
|
+
self._uid_label_params = None
|
|
3693
3771
|
|
|
3772
|
+
if not self.uid_or_label is None:
|
|
3773
|
+
F = self.uid_or_label
|
|
3774
|
+
which = "'uid'" if not uid is None else "'label'"
|
|
3775
|
+
if isinstance( F, str ):
|
|
3776
|
+
r = _expected_str_fmt_args( F )
|
|
3777
|
+
if len(r.positional) + len(r.posindices) > 0:
|
|
3778
|
+
raise ValueError("f{which} '{F}' cannot have positional arguments (empty brackets {} or brackets with integer position {1}). Use only named arguments.")
|
|
3779
|
+
self._uid_label_params = list(r.keywords)
|
|
3780
|
+
del r
|
|
3781
|
+
else:
|
|
3782
|
+
if not inspect.isfunction(F):
|
|
3783
|
+
if not callable(F):
|
|
3784
|
+
raise ValueError(f"{which} '{_qualified_name(F)}' is not callable")
|
|
3785
|
+
F = F.__call__
|
|
3786
|
+
assert inspect.isfunction(F), ("Internal error - function expected")
|
|
3787
|
+
self._uid_label_params = list( inspect.signature(F).parameters )
|
|
3788
|
+
del F, which
|
|
3789
|
+
self._uid_label_params = self._uid_label_params if len(self._uid_label_params) > 0 else None
|
|
3790
|
+
|
|
3694
3791
|
@property
|
|
3695
3792
|
def uid_or_label(self) -> Callable:
|
|
3696
3793
|
""" ID or label """
|
|
@@ -3723,6 +3820,10 @@ class CacheCallable(object):
|
|
|
3723
3820
|
def global_exclude_arg_types(self) -> list[type]:
|
|
3724
3821
|
""" Returns ``exclude_arg_types`` of the underlying :class:`cdxcore.subdir.CacheController` """
|
|
3725
3822
|
return self.cache_controller.exclude_arg_types
|
|
3823
|
+
@property
|
|
3824
|
+
def uid_label_params(self) -> list:
|
|
3825
|
+
""" Returns the ``set`` of parameters the ``uid`` or ``label`` function expects """
|
|
3826
|
+
return self._uid_label_params
|
|
3726
3827
|
|
|
3727
3828
|
def __call__(self, F : Callable):
|
|
3728
3829
|
"""
|
|
@@ -3774,7 +3875,7 @@ class CacheCallable(object):
|
|
|
3774
3875
|
# apply version
|
|
3775
3876
|
# this also ensures that __init__ picks up a version dependency on the class itse
|
|
3776
3877
|
# (as we forceed 'auto_class' to be true)
|
|
3777
|
-
C = _ensure_has_version( C, version=self.
|
|
3878
|
+
C = _ensure_has_version( C, version=self._input_version,
|
|
3778
3879
|
dependencies=self._dependencies,
|
|
3779
3880
|
auto_class=self._version_auto_class)
|
|
3780
3881
|
|
|
@@ -3795,7 +3896,7 @@ class CacheCallable(object):
|
|
|
3795
3896
|
# Cannot currently decorate classes.
|
|
3796
3897
|
|
|
3797
3898
|
|
|
3798
|
-
is_method = inspect.ismethod(F)
|
|
3899
|
+
is_method = inspect.ismethod(F) # for *bound* methods
|
|
3799
3900
|
if is_method:
|
|
3800
3901
|
assert not getattr(F, "__self__", None) is None, ("Method type must have __self__...?", F.__qualname__ )
|
|
3801
3902
|
elif not inspect.isfunction(F):
|
|
@@ -3858,11 +3959,12 @@ class CacheCallable(object):
|
|
|
3858
3959
|
# -------
|
|
3859
3960
|
# Decorate now or pick up existing @version
|
|
3860
3961
|
|
|
3861
|
-
F = _ensure_has_version( F, version=self.
|
|
3962
|
+
F = _ensure_has_version( F, version=self._input_version,
|
|
3862
3963
|
dependencies=self._dependencies,
|
|
3863
3964
|
auto_class=self._version_auto_class,
|
|
3864
3965
|
allow_default=is_new )
|
|
3865
|
-
|
|
3966
|
+
version = F.version.unique_id64
|
|
3967
|
+
|
|
3866
3968
|
# name
|
|
3867
3969
|
# ----
|
|
3868
3970
|
|
|
@@ -3892,38 +3994,46 @@ class CacheCallable(object):
|
|
|
3892
3994
|
|
|
3893
3995
|
uid_or_label = self.uid_or_label
|
|
3894
3996
|
filename = None
|
|
3895
|
-
if
|
|
3896
|
-
#
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
try:
|
|
3900
|
-
filename = uid_or_label.format() # throws a KeyError if 'id' contains formatting information
|
|
3901
|
-
except KeyError:
|
|
3902
|
-
pass
|
|
3997
|
+
if self.unique and self._uid_label_params is None:
|
|
3998
|
+
# the string or function do not require any parameters, and is unique
|
|
3999
|
+
assert not uid_or_label is None
|
|
4000
|
+
filename = uid_or_label if isinstance( uid_or_label, str ) else uid_or_label()
|
|
3903
4001
|
|
|
3904
|
-
if not filename is None:
|
|
3905
|
-
# generate name with the unique string provided by the user
|
|
3906
4002
|
if not is_filename(filename):
|
|
3907
4003
|
raise ValueError(f"The unique filename '{filename}' computed for '{name}' contains invalid characters for filename. When using `uid` make sure that "+\
|
|
3908
|
-
"the returned ID is a valid filename (and unique)")
|
|
4004
|
+
"the returned ID is a valid filename (and is unique)")
|
|
3909
4005
|
label = filename
|
|
3910
|
-
filename = self.uniqueFileName( filename )
|
|
3911
4006
|
arguments = None
|
|
3912
|
-
|
|
4007
|
+
|
|
3913
4008
|
else:
|
|
4009
|
+
# need the list of parameters to compute a hash and/or a label
|
|
4010
|
+
which = 'uid' if not self._uid is None else 'label'
|
|
4011
|
+
|
|
3914
4012
|
# get dictionary of named arguments
|
|
3915
4013
|
arguments = execute.cache_info.signature.bind(*args,**kwargs)
|
|
3916
4014
|
arguments.apply_defaults()
|
|
3917
4015
|
arguments = arguments.arguments # ordered dict
|
|
3918
4016
|
|
|
4017
|
+
# delete 'cls' from argument list for class functions
|
|
3919
4018
|
if is_new:
|
|
3920
|
-
# delete 'cls' from argument list
|
|
3921
4019
|
assert len(arguments) >= 1, ("*** Internal error", F.__qualname__, is_new, arguments)
|
|
3922
4020
|
del arguments[list(arguments)[0]]
|
|
3923
|
-
|
|
4021
|
+
|
|
4022
|
+
# add 'self' for methods
|
|
4023
|
+
if is_method:
|
|
4024
|
+
# add __self__ to the beginning of all arguments
|
|
4025
|
+
full_arguments = OrderedDict()
|
|
4026
|
+
if is_method:
|
|
4027
|
+
if 'self' in set(arguments):
|
|
4028
|
+
raise RuntimeError(f"'self' found in bound method '{name}' argument list {fmt_dict(execute.cache_info.signature.bind(*args,**kwargs).arguments)}.")
|
|
4029
|
+
full_arguments['self'] = F.__self__
|
|
4030
|
+
full_arguments |= arguments
|
|
4031
|
+
arguments = full_arguments
|
|
4032
|
+
del full_arguments
|
|
3924
4033
|
|
|
3925
4034
|
# filter dictionary
|
|
3926
4035
|
if not self._exclude_args is None or not self._include_args is None:
|
|
4036
|
+
argus = set(arguments)
|
|
3927
4037
|
excl = set(self._exclude_args) if not self._exclude_args is None else set()
|
|
3928
4038
|
if not self._exclude_args is None:
|
|
3929
4039
|
if self._exclude_args > argus:
|
|
@@ -3937,7 +4047,7 @@ class CacheCallable(object):
|
|
|
3937
4047
|
for arg in excl:
|
|
3938
4048
|
if arg in arguments:
|
|
3939
4049
|
del arguments[arg]
|
|
3940
|
-
del excl
|
|
4050
|
+
del excl, argus
|
|
3941
4051
|
|
|
3942
4052
|
if len(exclude_types) > 0:
|
|
3943
4053
|
excl = []
|
|
@@ -3948,41 +4058,45 @@ class CacheCallable(object):
|
|
|
3948
4058
|
if arg in arguments:
|
|
3949
4059
|
del arguments[arg]
|
|
3950
4060
|
|
|
3951
|
-
# did the user provide a label or unique ID?
|
|
3952
4061
|
if uid_or_label is None:
|
|
4062
|
+
# no label or unique ID
|
|
4063
|
+
assert not self.unique
|
|
3953
4064
|
uid_or_label = name
|
|
3954
4065
|
|
|
4066
|
+
elif self._uid_label_params is None:
|
|
4067
|
+
# label function or string does not need any parameters
|
|
4068
|
+
assert not self.unique
|
|
4069
|
+
uid_or_label = uid_or_label if isinstance( uid_or_label, str ) else uid_or_label()
|
|
4070
|
+
|
|
3955
4071
|
else:
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
4072
|
+
# function or format string required parameters
|
|
4073
|
+
# add parameters in order of label/uid parameters
|
|
4074
|
+
assert not self._uid_label_params is None
|
|
4075
|
+
|
|
4076
|
+
fmt_arguments = {}
|
|
4077
|
+
for k in self._uid_label_params:
|
|
4078
|
+
if k == self._name_of_func_name_arg:
|
|
4079
|
+
if self._name_of_func_name_arg in arguments:
|
|
4080
|
+
error(f"{name}: '{self._name_of_func_name_arg}' is a reserved keyword for '{which}' which refers to the current function name. "
|
|
4081
|
+
"Found it also in the function parameter list. Use 'name_of_func_name_arg' to change the internal parameter name used.")
|
|
4082
|
+
fmt_arguments[k] = name
|
|
4083
|
+
else:
|
|
4084
|
+
if not k in arguments:
|
|
4085
|
+
args_ = [ f"'{_}'" for _ in arguments ]
|
|
4086
|
+
raise ValueError(f"Error while generating '{which}' for '{name}': formatting function expected a parameter '{k}' which is not present "+\
|
|
4087
|
+
f"in the list of parameters passed to '{name}': {fmt_list(args_)}.")
|
|
4088
|
+
fmt_arguments[k] = arguments[k]
|
|
3969
4089
|
|
|
3970
4090
|
# call format or function
|
|
3971
4091
|
if isinstance( uid_or_label, str ):
|
|
3972
|
-
|
|
3973
|
-
uid_or_label = str.format( uid_or_label, **arguments )
|
|
3974
|
-
except KeyError as e:
|
|
3975
|
-
raise KeyError(e, f"Error while generating id for '{name}' using format string '{uid_or_label}': {e}. Available arguments: {list(arguments)}")
|
|
3976
|
-
|
|
4092
|
+
uid_or_label = str.format( uid_or_label, **fmt_arguments )
|
|
3977
4093
|
else:
|
|
3978
|
-
which = 'uid' if not self._uid is None else 'label'
|
|
3979
4094
|
try:
|
|
3980
|
-
uid_or_label = uid_or_label(**
|
|
3981
|
-
except TypeError as e:
|
|
3982
|
-
raise TypeError(e, f"Error while generating '{which}' for '{name}' using a function: {e}. Available arguments: {list(arguments)}")
|
|
4095
|
+
uid_or_label = uid_or_label(**fmt_arguments)
|
|
3983
4096
|
except Exception as e:
|
|
3984
|
-
raise type(e)(f"Error while generating '{which}' for '{name}': attempt to call '{which}' of type {type(uid_or_label)} failed: {e}")
|
|
3985
|
-
|
|
4097
|
+
raise type(e)(f"Error while generating '{which}' for '{name}': attempt to call '{which}' of callable type {type(uid_or_label)} failed: {e}")
|
|
4098
|
+
if not isinstance(uid_or_label, str):
|
|
4099
|
+
raise ValueError("Error calling callable '{which}' for '{name}': callable must return a string. Found {type(uid_or_label))}")
|
|
3986
4100
|
|
|
3987
4101
|
if self.unique:
|
|
3988
4102
|
if not is_filename(uid_or_label):
|
|
@@ -3998,7 +4112,6 @@ class CacheCallable(object):
|
|
|
3998
4112
|
# determine version, cache mode
|
|
3999
4113
|
# ------------------
|
|
4000
4114
|
|
|
4001
|
-
version_ = self._version if not self._version is None else F.version.unique_id64
|
|
4002
4115
|
cache_mode = CacheMode(override_cache_mode) if not override_cache_mode is None else self.cache_mode
|
|
4003
4116
|
del override_cache_mode
|
|
4004
4117
|
|
|
@@ -4007,7 +4120,7 @@ class CacheCallable(object):
|
|
|
4007
4120
|
|
|
4008
4121
|
execute.cache_info.label = str(label) if not label is None else None
|
|
4009
4122
|
execute.cache_info.filename = filename # that is the unique ID for this call
|
|
4010
|
-
execute.cache_info.version =
|
|
4123
|
+
execute.cache_info.version = version
|
|
4011
4124
|
|
|
4012
4125
|
if self.cache_controller.keep_last_arguments:
|
|
4013
4126
|
info_arguments = OrderedDict()
|
|
@@ -4026,11 +4139,11 @@ class CacheCallable(object):
|
|
|
4026
4139
|
pass
|
|
4027
4140
|
tag = Tag()
|
|
4028
4141
|
if not is_new:
|
|
4029
|
-
r = self._subdir.read( filename, tag, version=
|
|
4142
|
+
r = self._subdir.read( filename, tag, version=version )
|
|
4030
4143
|
else:
|
|
4031
4144
|
try:
|
|
4032
4145
|
execute.__new_during_read = True
|
|
4033
|
-
r = self._subdir.read( filename, tag, version=
|
|
4146
|
+
r = self._subdir.read( filename, tag, version=version )
|
|
4034
4147
|
finally:
|
|
4035
4148
|
execute.__new_during_read = False
|
|
4036
4149
|
|
|
@@ -4039,7 +4152,7 @@ class CacheCallable(object):
|
|
|
4039
4152
|
track_cached_files += self._fullFileName(filename)
|
|
4040
4153
|
execute.cache_info.last_cached = True
|
|
4041
4154
|
if not debug_verbose is None:
|
|
4042
|
-
debug_verbose.write(f"cache({name}): read '{label}' version 'version {
|
|
4155
|
+
debug_verbose.write(f"cache({name}): read '{label}' version 'version {version}' from cache '{self._subdir.full_file_name(filename)}'.")
|
|
4043
4156
|
if is_new:
|
|
4044
4157
|
assert r.__magic_cache_call_init__ is None, ("**** Internal error. __init__ should reset __magic_cache_call_init__", F.__qualname__, label)
|
|
4045
4158
|
r.__magic_cache_call_init__ = False # since we called __new__, __init__ will be called next
|
|
@@ -4060,7 +4173,7 @@ class CacheCallable(object):
|
|
|
4060
4173
|
assert r.__magic_cache_call_init__ is None, ("**** Internal error. __init__ should reset __magic_cache_call_init__")
|
|
4061
4174
|
|
|
4062
4175
|
if cache_mode.write:
|
|
4063
|
-
self._subdir.write(filename,r,version=
|
|
4176
|
+
self._subdir.write(filename,r,version=version)
|
|
4064
4177
|
if not track_cached_files is None:
|
|
4065
4178
|
track_cached_files += self._subdir.full_file_name(filename)
|
|
4066
4179
|
execute.cache_info.last_cached = False
|
|
@@ -4072,9 +4185,9 @@ class CacheCallable(object):
|
|
|
4072
4185
|
|
|
4073
4186
|
if not debug_verbose is None:
|
|
4074
4187
|
if cache_mode.write:
|
|
4075
|
-
debug_verbose.write(f"cache({name}): called '{label}' version 'version {
|
|
4188
|
+
debug_verbose.write(f"cache({name}): called '{label}' version 'version {version}' and wrote result into '{self._subdir.full_file_name(filename)}'.")
|
|
4076
4189
|
else:
|
|
4077
|
-
debug_verbose.write(f"cache({name}): called '{label}' version 'version {
|
|
4190
|
+
debug_verbose.write(f"cache({name}): called '{label}' version 'version {version}' but did *not* write into '{self._subdir.full_file_name(filename)}'.")
|
|
4078
4191
|
|
|
4079
4192
|
if return_cache_uid:
|
|
4080
4193
|
return filename, r
|
|
@@ -266,11 +266,10 @@ class UniqueHash( object ):
|
|
|
266
266
|
""" Return copy of `self`. """
|
|
267
267
|
return UniqueHash( **{ k:v for k,v in self.__dict__.items() if not k[:1] == "_"} )
|
|
268
268
|
|
|
269
|
-
def __call__(
|
|
269
|
+
def __call__(__self__, # LEAVE THIS NAME. **kwargs might contain 'self' arguments.
|
|
270
|
+
*args, debug_trace : DebugTrace = None, **kwargs) -> str:
|
|
270
271
|
"""
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
Returns a unique hash for the `arg` and `kwargs` parameters passed to this function.
|
|
272
|
+
Returns a unique hash for the ``arg`` and ``kwargs`` parameters passed to this function.
|
|
274
273
|
|
|
275
274
|
Example::
|
|
276
275
|
|
|
@@ -289,7 +288,7 @@ class UniqueHash( object ):
|
|
|
289
288
|
args, kwargs:
|
|
290
289
|
Parameters to hash.
|
|
291
290
|
|
|
292
|
-
debug_trace : :class:`cdxcore.uniquehash.DebugTrace`
|
|
291
|
+
debug_trace : :class:`cdxcore.uniquehash.DebugTrace` | None, default ``None``
|
|
293
292
|
Allows tracing of hashing activity for debugging purposes.
|
|
294
293
|
Two implementations of ``DebugTrace`` are available:
|
|
295
294
|
|
|
@@ -302,13 +301,13 @@ class UniqueHash( object ):
|
|
|
302
301
|
Returns
|
|
303
302
|
-------
|
|
304
303
|
Hash : str
|
|
305
|
-
String of at most
|
|
304
|
+
String of at most ``self.length``.
|
|
306
305
|
"""
|
|
307
|
-
h, _ =
|
|
306
|
+
h, _ = __self__._mk_blake( h=__self__.length//2 )
|
|
308
307
|
if len(args) > 0:
|
|
309
|
-
|
|
308
|
+
__self__._hash_any( h, args, debug_trace = debug_trace )
|
|
310
309
|
if len(kwargs) > 0:
|
|
311
|
-
|
|
310
|
+
__self__._hash_any( h, kwargs, debug_trace = debug_trace )
|
|
312
311
|
return h.hexdigest()
|
|
313
312
|
|
|
314
313
|
# Utility functions
|
|
@@ -655,7 +655,13 @@ def version( version : str = "0.0.1" ,
|
|
|
655
655
|
if not gversion._class is None:
|
|
656
656
|
continue
|
|
657
657
|
gversion._class = f
|
|
658
|
-
|
|
658
|
+
|
|
659
|
+
version_ = Version(f, version, dep, auto_class=auto_class )
|
|
660
|
+
try:
|
|
661
|
+
f.version = version_
|
|
662
|
+
except AttributeError:
|
|
663
|
+
f.__dict__['version'] = version_
|
|
664
|
+
del version_
|
|
659
665
|
assert type(f.version).__name__ == Version.__name__
|
|
660
666
|
return f
|
|
661
667
|
return wrap
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "cdxcore"
|
|
9
|
-
version = "0.1.
|
|
9
|
+
version = "0.1.29"
|
|
10
10
|
description = "Basic Python Tools; upgraded cdxbasics"
|
|
11
11
|
authors = [{ name = "Hans Buehler", email = "github@buehler.london" }]
|
|
12
12
|
readme = "README.md"
|
|
@@ -26,6 +26,7 @@ import_local()
|
|
|
26
26
|
Imports
|
|
27
27
|
"""
|
|
28
28
|
from cdxcore.subdir import SubDir, CacheMode, VersionError, VersionPresentError, VersionedCacheRoot
|
|
29
|
+
from cdxcore.version import version
|
|
29
30
|
import numpy as np
|
|
30
31
|
|
|
31
32
|
class Test(unittest.TestCase):
|
|
@@ -277,6 +278,15 @@ class A(object):
|
|
|
277
278
|
class B(object):
|
|
278
279
|
def __init__(self, x):
|
|
279
280
|
self.x = x
|
|
281
|
+
|
|
282
|
+
raw_sub = VersionedCacheRoot("?/subdir_cache_test2", exclude_arg_types=[A] )
|
|
283
|
+
|
|
284
|
+
class C(object):
|
|
285
|
+
def __init__(self, x):
|
|
286
|
+
self.x = x
|
|
287
|
+
@raw_sub.cache("0.1")
|
|
288
|
+
def f(self, y):
|
|
289
|
+
return self.x*y
|
|
280
290
|
|
|
281
291
|
class Test(unittest.TestCase):
|
|
282
292
|
|
|
@@ -311,7 +321,7 @@ class Test(unittest.TestCase):
|
|
|
311
321
|
_ = f(A(2))
|
|
312
322
|
self.assertTrue( f.cache_info.last_cached )
|
|
313
323
|
|
|
314
|
-
@sub.cache("1.0", label=lambda x
|
|
324
|
+
@sub.cache("1.0", label=lambda x: f"f({x})")
|
|
315
325
|
def f(x):
|
|
316
326
|
return x
|
|
317
327
|
|
|
@@ -320,7 +330,7 @@ class Test(unittest.TestCase):
|
|
|
320
330
|
uid, _ = f(1, return_cache_uid=True)
|
|
321
331
|
self.assertEqual( uid[:5], "f(1) " )
|
|
322
332
|
|
|
323
|
-
@sub.cache("1.0", uid=lambda x
|
|
333
|
+
@sub.cache("1.0", uid=lambda x: f"f({x})")
|
|
324
334
|
def f(x):
|
|
325
335
|
return x
|
|
326
336
|
|
|
@@ -329,6 +339,77 @@ class Test(unittest.TestCase):
|
|
|
329
339
|
uid, _ = f(1, return_cache_uid=True)
|
|
330
340
|
self.assertEqual( uid, "f(1)" )
|
|
331
341
|
|
|
342
|
+
# test member caching
|
|
343
|
+
|
|
344
|
+
c = C(2.)
|
|
345
|
+
_ = c.f(3)
|
|
346
|
+
self.assertFalse( c.f.cache_info.last_cached )
|
|
347
|
+
_ = c.f(3)
|
|
348
|
+
self.assertTrue( c.f.cache_info.last_cached )
|
|
349
|
+
_ = c.f(2)
|
|
350
|
+
self.assertFalse( c.f.cache_info.last_cached )
|
|
351
|
+
|
|
352
|
+
# test versioning
|
|
353
|
+
|
|
354
|
+
@version("F")
|
|
355
|
+
def F(x):
|
|
356
|
+
return x
|
|
357
|
+
|
|
358
|
+
@sub.cache("G")
|
|
359
|
+
def G(x):
|
|
360
|
+
return x
|
|
361
|
+
|
|
362
|
+
@sub.cache("H", dependencies=[F,G])
|
|
363
|
+
def H(x):
|
|
364
|
+
return G(x)*F(x)
|
|
365
|
+
|
|
366
|
+
_ = H(2.)
|
|
367
|
+
self.assertEqual( H.cache_info.version, "H { Test.test_cache.<locals>.F: F, Test.test_cache.<loc 3fabc694" )
|
|
368
|
+
self.assertEqual( H.cache_info.version, H.version.unique_id64 )
|
|
369
|
+
|
|
370
|
+
# decorate live member functions
|
|
371
|
+
|
|
372
|
+
class AA(object):
|
|
373
|
+
def __init__(self,x):
|
|
374
|
+
self.x = x
|
|
375
|
+
def f(self,y):
|
|
376
|
+
return self.x*y
|
|
377
|
+
|
|
378
|
+
a = AA(x=1)
|
|
379
|
+
f = sub.cache("0.1", label=lambda y : f"a.f({y})")(a.f) # <- decorate bound 'f'.
|
|
380
|
+
_ = f(y=2)
|
|
381
|
+
self.assertFalse( f.cache_info.last_cached )
|
|
382
|
+
self.assertEqual( f.cache_info.version, "0.1" )
|
|
383
|
+
_ = f(y=2)
|
|
384
|
+
self.assertTrue( f.cache_info.last_cached )
|
|
385
|
+
|
|
386
|
+
# funcname
|
|
387
|
+
|
|
388
|
+
sub = VersionedCacheRoot("?/subdir_cache_test", exclude_arg_types=[A], keep_last_arguments=True)
|
|
389
|
+
|
|
390
|
+
@sub.cache("0.1", label=lambda new_func_name, func_name, x, y : f"{new_func_name}(): {func_name} {x} {y}", name_of_func_name_arg="new_func_name")
|
|
391
|
+
def f( func_name, x, y ):
|
|
392
|
+
pass
|
|
393
|
+
f("test",1,y=2)
|
|
394
|
+
self.assertEqual( repr(f.cache_info.arguments), "OrderedDict({'func_name': 'test', 'x': '1', 'y': '2'})" )
|
|
395
|
+
|
|
396
|
+
# this should just work
|
|
397
|
+
@sub.cache("0.1", label=lambda func_name, x : f"{func_name} {x}")
|
|
398
|
+
def f( func_name, x ):
|
|
399
|
+
pass
|
|
400
|
+
with self.assertRaises(RuntimeError):
|
|
401
|
+
f("test",1)
|
|
402
|
+
|
|
403
|
+
# cuttinf off
|
|
404
|
+
@sub.cache("0.1", uid=lambda x,y: f"h2({x},{y})_______________________________________________________________________", exclude_args='debug')
|
|
405
|
+
def h2(x,y,debug=False):
|
|
406
|
+
if debug:
|
|
407
|
+
print(f"h(x={x},y={y})")
|
|
408
|
+
return x*y
|
|
409
|
+
h2(1,1)
|
|
410
|
+
# %%
|
|
411
|
+
self.assertEqual( h2.cache_info.filename, "h2(1,1)________________________________ 46a70d67" )
|
|
412
|
+
|
|
332
413
|
|
|
333
414
|
if __name__ == '__main__':
|
|
334
415
|
unittest.main()
|
|
@@ -457,16 +457,25 @@ class Test(unittest.TestCase):
|
|
|
457
457
|
def __unique_hash__( self, unique_hash, debug_trace ):
|
|
458
458
|
return ( self._seed, self._size )
|
|
459
459
|
|
|
460
|
+
class E(A):
|
|
461
|
+
""" Fixed string """
|
|
462
|
+
def __init__(self):
|
|
463
|
+
self.__unique_hash__ = "some_string"
|
|
464
|
+
|
|
460
465
|
empty = unique_hash( ('Test.test_uniqueHash.<locals>.A') )
|
|
461
466
|
hash1 = unique_hash( A() )
|
|
462
467
|
hash2 = unique_hash( B() )
|
|
463
468
|
hash3 = unique_hash( C() )
|
|
464
469
|
hash4 = unique_hash( D() )
|
|
470
|
+
hash5 = unique_hash( E() )
|
|
471
|
+
h_5 = unique_hash( E().__unique_hash__ )
|
|
465
472
|
self.assertEqual( empty, "653a05ac14649dbceebbd7f3d4a0b89f" )
|
|
466
473
|
self.assertEqual( hash1, empty )
|
|
467
474
|
self.assertEqual( hash2, "ae7cc6d56596eaa20dcd6aedc6e89d85" )
|
|
468
475
|
self.assertEqual( hash3, "5e387f9e86426319577d2121a4e1437b" )
|
|
469
476
|
self.assertEqual( hash4, "c9b449e95339458df155752acadbebb1" )
|
|
477
|
+
self.assertEqual( hash5, "79ee12b070c036e874263c6f4d70df98" )
|
|
478
|
+
self.assertEqual( hash5, h_5 )
|
|
470
479
|
|
|
471
480
|
|
|
472
481
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|