cdxcore 0.1.27__py3-none-any.whl → 0.1.29__py3-none-any.whl
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/__init__.py +1 -1
- cdxcore/subdir.py +360 -135
- cdxcore/uniquehash.py +8 -9
- cdxcore/version.py +7 -1
- {cdxcore-0.1.27.dist-info → cdxcore-0.1.29.dist-info}/METADATA +1 -1
- {cdxcore-0.1.27.dist-info → cdxcore-0.1.29.dist-info}/RECORD +11 -11
- tests/test_subdir.py +143 -7
- tests/test_uniquehash.py +9 -0
- {cdxcore-0.1.27.dist-info → cdxcore-0.1.29.dist-info}/WHEEL +0 -0
- {cdxcore-0.1.27.dist-info → cdxcore-0.1.29.dist-info}/licenses/LICENSE +0 -0
- {cdxcore-0.1.27.dist-info → cdxcore-0.1.29.dist-info}/top_level.txt +0 -0
cdxcore/__init__.py
CHANGED
cdxcore/subdir.py
CHANGED
|
@@ -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
|
|
@@ -3178,9 +3187,13 @@ class SubDir(object):
|
|
|
3178
3187
|
:dec:`cdxcore.subdir.SubDir.cache`
|
|
3179
3188
|
to provide version information at class level. Only version information are provided here.
|
|
3180
3189
|
|
|
3190
|
+
You can use :dec:`cdxcore.subdir.SubDir.cache_class` as an alias.
|
|
3191
|
+
|
|
3181
3192
|
2) Secondly, decorate ``__init__``. You do not need to specify a version
|
|
3182
3193
|
for ``__init__`` as its version usually coincides with the version of the class. At ``__init__``
|
|
3183
3194
|
you define how unique IDs are generated from the parameters passed to object construction.
|
|
3195
|
+
|
|
3196
|
+
You can use :dec:`cdxcore.subdir.SubDir.cache_init` as an alias.
|
|
3184
3197
|
|
|
3185
3198
|
Simple example:
|
|
3186
3199
|
|
|
@@ -3190,10 +3203,10 @@ class SubDir(object):
|
|
|
3190
3203
|
cache = SubDir("!/.cache")
|
|
3191
3204
|
cache.delete_all_content() # for illustration
|
|
3192
3205
|
|
|
3193
|
-
@cache.
|
|
3206
|
+
@cache.cache_class("0.1")
|
|
3194
3207
|
class A(object):
|
|
3195
3208
|
|
|
3196
|
-
@cache.
|
|
3209
|
+
@cache.cache_init(exclude_args=['debug'])
|
|
3197
3210
|
def __init__(self, x, debug):
|
|
3198
3211
|
if debug:
|
|
3199
3212
|
print("__init__",x)
|
|
@@ -3216,10 +3229,10 @@ class SubDir(object):
|
|
|
3216
3229
|
|
|
3217
3230
|
.. code-block:: python
|
|
3218
3231
|
|
|
3219
|
-
@cache.
|
|
3232
|
+
@cache.cache_class("0.1")
|
|
3220
3233
|
class A(object):
|
|
3221
3234
|
|
|
3222
|
-
@cache.
|
|
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 **_
|
|
3223
3236
|
def __init__(self, x, debug):
|
|
3224
3237
|
if debug:
|
|
3225
3238
|
print("__init__",x)
|
|
@@ -3227,77 +3240,162 @@ class SubDir(object):
|
|
|
3227
3240
|
|
|
3228
3241
|
Decorating classes with ``__slots__`` does not yet work.
|
|
3229
3242
|
|
|
3230
|
-
|
|
3231
|
-
|
|
3243
|
+
Managing Caching Accross a Project
|
|
3244
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
3232
3245
|
|
|
3233
|
-
For project-wide use it is usually
|
|
3246
|
+
For project-wide use it is usually convenient to control caching at the level of a
|
|
3234
3247
|
project-wide cache root directory.
|
|
3235
|
-
|
|
3248
|
+
The classs :class:`cdxcore.subdir.VersionedCacheRoot` is a thin convenience wrapper around a :class:`cdxcore.subdir.SubDir`
|
|
3236
3249
|
with a :class:`cdxcore.subdir.CacheController`.
|
|
3237
3250
|
|
|
3251
|
+
The idea is to have a central file, ``cache.py`` which contains the central root for caching.
|
|
3252
|
+
We recommend using an environment variable to be able to control the location of this directory
|
|
3253
|
+
out side the code. Here is an example with an environment variable ``PROJECT_CACHE_DIR``::
|
|
3254
|
+
|
|
3255
|
+
# file cache.py
|
|
3256
|
+
|
|
3257
|
+
from cdxcore.subdir import VersionedCacheRoot
|
|
3258
|
+
import os as os
|
|
3259
|
+
|
|
3260
|
+
cache_root = VersionedCacheRoot(
|
|
3261
|
+
os.getenv("PROJECT_CACHE_DIR", "!/.cache")
|
|
3262
|
+
)
|
|
3263
|
+
|
|
3264
|
+
In a particular project file, say ``pipeline.py`` create a file-local cache directory
|
|
3265
|
+
and use it::
|
|
3266
|
+
|
|
3267
|
+
# file pipeline.py
|
|
3268
|
+
|
|
3269
|
+
from cache import cache_root
|
|
3270
|
+
|
|
3271
|
+
cache_dir = cache_root("pipeline")
|
|
3272
|
+
|
|
3273
|
+
@cache_dir.cache("0.1")
|
|
3274
|
+
def f(x):
|
|
3275
|
+
return x+2
|
|
3276
|
+
|
|
3277
|
+
@cache_dir.cache("0.1", dependencies=[f])
|
|
3278
|
+
def g(x)
|
|
3279
|
+
return f(x)**2
|
|
3280
|
+
|
|
3281
|
+
# ...
|
|
3282
|
+
|
|
3283
|
+
In case you have issues with caching you can use the central root directory to turn on tracing
|
|
3284
|
+
accross your project:
|
|
3285
|
+
|
|
3286
|
+
.. code-block:: python
|
|
3287
|
+
:emphasize-lines: 4
|
|
3288
|
+
|
|
3289
|
+
from cdxcore.verbose import Context
|
|
3290
|
+
cache_root = VersionedCacheRoot(
|
|
3291
|
+
os.getenv("PROJECT_CACHE_DIR", "!/.cache"),
|
|
3292
|
+
debug_verbose=Context.all # turn full traing on
|
|
3293
|
+
)
|
|
3294
|
+
|
|
3238
3295
|
Parameters
|
|
3239
3296
|
----------
|
|
3240
|
-
version : str,
|
|
3297
|
+
version : str | None, default ``None``
|
|
3241
3298
|
Version of the function.
|
|
3242
3299
|
|
|
3243
|
-
* If ``None`` then
|
|
3244
|
-
* If set, the function ``F`` is first decorated with :dec:`cdxcore.version.version
|
|
3300
|
+
* If ``None`` then a common `F`` must be decorated manually with :dec:`cdxcore.version.version`.
|
|
3301
|
+
* If set, the function ``F`` is automatically first decorated with :dec:`cdxcore.version.version` for you.
|
|
3245
3302
|
|
|
3246
|
-
dependencies : list[type],
|
|
3303
|
+
dependencies : list[type] | None, default ``None``
|
|
3247
3304
|
A list of version dependencies, either by reference or by name.
|
|
3248
3305
|
See :dec:`cdxcore.version.version` for details on name lookup if strings are used.
|
|
3249
3306
|
|
|
3250
|
-
label : str | Callable
|
|
3307
|
+
label : str | Callable | None, default ``None``
|
|
3251
3308
|
Specify a human-readable label for the function call given its parameters.
|
|
3309
|
+
|
|
3252
3310
|
This label is used to generate the cache file name, and is also printed in when tracing
|
|
3253
3311
|
hashing operations. Labels are not assumed to be unique, hence a unique hash of
|
|
3254
3312
|
the label and the parameters to this function will be appended to generate
|
|
3255
3313
|
the actual cache file name.
|
|
3256
3314
|
|
|
3257
|
-
Use ``uid`` instead if ``label`` represents valid unique filenames.
|
|
3258
|
-
|
|
3315
|
+
Use ``uid`` instead if ``label`` represents valid unique filenames. You cannot specify both ``uid`` and ``label``.
|
|
3316
|
+
If neither ``uid`` and ``label`` are present, ``name`` will be used.
|
|
3259
3317
|
|
|
3260
3318
|
**Usage:**
|
|
3261
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
|
+
|
|
3262
3327
|
* If ``label`` is a plain string without ``{}`` formatting: use this string as-is.
|
|
3263
3328
|
|
|
3264
|
-
* If ``label`` is a string with ``{}`` formatting, then ``label.format(
|
|
3265
|
-
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.
|
|
3266
3335
|
|
|
3267
|
-
* If ``label`` is a ``Callable`` then ``label( name=name, **parameters )`` will be called
|
|
3268
|
-
to generate the actual label.
|
|
3269
|
-
|
|
3270
3336
|
See above for examples.
|
|
3271
3337
|
|
|
3272
3338
|
``label`` cannot be used alongside ``uid``.
|
|
3273
3339
|
|
|
3274
|
-
uid : str | Callable
|
|
3340
|
+
uid : str | Callable | None, default ``None``
|
|
3275
3341
|
Alternative to ``label`` which is assumed to generate a unique cache file name. It has the same
|
|
3276
|
-
semantics as ``label``. When used, parameters to the decorated function are not hashed
|
|
3342
|
+
semantics as ``label``. When used, parameters to the decorated function are not hashed
|
|
3343
|
+
as the ``uid`` is assumed to be already unique. The string must be a valid file name
|
|
3277
3344
|
|
|
3278
|
-
``
|
|
3345
|
+
Use ``label`` if the id is not unique. You cannot specify both ``uid`` and ``label``.
|
|
3346
|
+
If neither ``uid`` and ``label`` are present, ``name`` will be used (as non-unique ``label``).
|
|
3279
3347
|
|
|
3280
|
-
name : str,
|
|
3281
|
-
Name of this function which is used either on its own if neither ``label`` not ``uid`` are used
|
|
3282
|
-
|
|
3283
|
-
formatting operator.
|
|
3348
|
+
name : str | None, default ``None``
|
|
3349
|
+
Name of this function which is used either on its own if neither ``label`` not ``uid`` are used,
|
|
3350
|
+
or which passed as a parameter ``func_name`` to either the callable or the
|
|
3351
|
+
formatting operator. See above for more details.
|
|
3284
3352
|
|
|
3285
3353
|
If ``name`` is not specified it defaults to ``__qualname__`` expanded
|
|
3286
3354
|
by the module name the function is defined in.
|
|
3287
3355
|
|
|
3288
|
-
include_args : list[str]
|
|
3356
|
+
include_args : list[str] | None, default ``None``
|
|
3289
3357
|
List of arguments to include in generating an unqiue ID, or ``None`` for all.
|
|
3290
3358
|
|
|
3291
|
-
exclude_args : list[str]
|
|
3292
|
-
List of arguments to exclude from generating an unique ID.
|
|
3359
|
+
exclude_args : list[str] | None, default ``None``
|
|
3360
|
+
List of arguments to exclude from generating an unique ID. Examples of such non-functional arguments
|
|
3361
|
+
are workflow controls (debugging) and i/o elements.
|
|
3293
3362
|
|
|
3294
|
-
exclude_arg_types : list[type]
|
|
3295
|
-
List of parameter types to exclude from generating an unique ID.
|
|
3363
|
+
exclude_arg_types : list[type] | None, default ``None``
|
|
3364
|
+
List of parameter types to exclude from generating an unique ID. Examples of such non-functional arguments
|
|
3365
|
+
are workflow controls (debugging) and i/o elements.
|
|
3296
3366
|
|
|
3297
|
-
version_auto_class : bool
|
|
3367
|
+
version_auto_class : bool, default ``True``
|
|
3298
3368
|
Whether to automaticallty add version dependencies on base classes or, for member functions, on containing
|
|
3299
3369
|
classes. This is the ``auto_class`` parameter for :dec:`cdxcore.version.version`.
|
|
3300
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
|
+
|
|
3301
3399
|
Returns
|
|
3302
3400
|
-------
|
|
3303
3401
|
Decorated F: Callable
|
|
@@ -3326,12 +3424,39 @@ class SubDir(object):
|
|
|
3326
3424
|
|
|
3327
3425
|
The decorated ``F()`` has additional function parameters, namely:
|
|
3328
3426
|
|
|
3329
|
-
* ``override_cache_mode`` :
|
|
3330
|
-
|
|
3427
|
+
* ``override_cache_mode`` : ``CacheMode`` | None, default ``None``
|
|
3428
|
+
|
|
3429
|
+
Allows overriding the ``CacheMode`` temporarily, in particular you can set it to ``"off"``.
|
|
3430
|
+
|
|
3431
|
+
* ``track_cached_files`` : :class:`cdxcore.subdir.CacheTracker` | None, default ``None``
|
|
3432
|
+
|
|
3433
|
+
Allows passing a :class:`cdxcore.subdir.CacheTracker`
|
|
3331
3434
|
object to keep track of all
|
|
3332
3435
|
files used (loaded from or saved to).
|
|
3333
3436
|
The function :meth:`cdxcore.subdir.CacheTracker.delete_cache_files` can be used
|
|
3334
3437
|
to delete all files involved in caching.
|
|
3438
|
+
|
|
3439
|
+
* ``return_cache_uid`` : bool, default ``False``
|
|
3440
|
+
|
|
3441
|
+
If ``True``, then the decorated function will return a tuple ``uid, result``
|
|
3442
|
+
where ``uid`` is the unique filename generated for this function call,
|
|
3443
|
+
and where ``result`` is the actual result from the function, cached or not.
|
|
3444
|
+
|
|
3445
|
+
Usage::
|
|
3446
|
+
|
|
3447
|
+
from cdxcore.subdir import SubDir
|
|
3448
|
+
cache_dir = SubDir("!/.cache")
|
|
3449
|
+
|
|
3450
|
+
@cache_dir.cache()
|
|
3451
|
+
def f(x, y):
|
|
3452
|
+
return x*y
|
|
3453
|
+
|
|
3454
|
+
uid, xy = f( x=1, y=2, return_cache_uid=True )
|
|
3455
|
+
|
|
3456
|
+
This pattern is thread-safe when compared to using::
|
|
3457
|
+
|
|
3458
|
+
xy = f( x=1, y=2 )
|
|
3459
|
+
uid = f.cache_info.filename
|
|
3335
3460
|
"""
|
|
3336
3461
|
return CacheCallable(subdir = self,
|
|
3337
3462
|
version = version,
|
|
@@ -3342,7 +3467,8 @@ class SubDir(object):
|
|
|
3342
3467
|
exclude_args = exclude_args,
|
|
3343
3468
|
include_args = include_args,
|
|
3344
3469
|
exclude_arg_types = exclude_arg_types,
|
|
3345
|
-
version_auto_class = version_auto_class
|
|
3470
|
+
version_auto_class = version_auto_class,
|
|
3471
|
+
name_of_func_name_arg = name_of_func_name_arg)
|
|
3346
3472
|
|
|
3347
3473
|
def cache_class( self,
|
|
3348
3474
|
version : str = None , *,
|
|
@@ -3361,17 +3487,41 @@ class SubDir(object):
|
|
|
3361
3487
|
@cache.cache_class("0.1")
|
|
3362
3488
|
class A(object):
|
|
3363
3489
|
|
|
3364
|
-
@cache.
|
|
3490
|
+
@cache.cache_init(exclude_args=['debug'])
|
|
3365
3491
|
def __init__(self, x, debug):
|
|
3366
3492
|
if debug:
|
|
3367
3493
|
print("__init__",x)
|
|
3368
3494
|
self.x = x
|
|
3369
3495
|
|
|
3370
3496
|
"""
|
|
3371
|
-
return self.cache( name=name,
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3497
|
+
return self.cache( name=name, version=version, dependencies=dependencies, version_auto_class=version_auto_class)
|
|
3498
|
+
|
|
3499
|
+
def cache_init( self,
|
|
3500
|
+
label : Callable = None,
|
|
3501
|
+
uid : Callable = None,
|
|
3502
|
+
exclude_args : list[str] = None,
|
|
3503
|
+
include_args : list[str] = None,
|
|
3504
|
+
exclude_arg_types : list[type] = None,
|
|
3505
|
+
):
|
|
3506
|
+
"""
|
|
3507
|
+
Short-cut for :dec:`cdxcore.subdir.SubDir.cache` applied to decorating ``__init__``
|
|
3508
|
+
with a reduced number of available parameters.
|
|
3509
|
+
|
|
3510
|
+
Example::
|
|
3511
|
+
|
|
3512
|
+
cache = SubDir("!/.cache")
|
|
3513
|
+
|
|
3514
|
+
@cache.cache_class("0.1")
|
|
3515
|
+
class A(object):
|
|
3516
|
+
|
|
3517
|
+
@cache.cache_init(exclude_args=['debug'])
|
|
3518
|
+
def __init__(self, x, debug):
|
|
3519
|
+
if debug:
|
|
3520
|
+
print("__init__",x)
|
|
3521
|
+
self.x = x
|
|
3522
|
+
|
|
3523
|
+
"""
|
|
3524
|
+
return self.cache( label=label, uid=uid, exclude_args=exclude_args, include_args=include_args, exclude_arg_types=exclude_arg_types )
|
|
3375
3525
|
|
|
3376
3526
|
# ========================================================================
|
|
3377
3527
|
# Caching, convenience
|
|
@@ -3444,6 +3594,9 @@ def VersionedCacheRoot( directory : str, *,
|
|
|
3444
3594
|
|
|
3445
3595
|
* ``hash_length``: length used for hashes, see :class:`cdxcore.uniquehash.UniqueHash`.
|
|
3446
3596
|
|
|
3597
|
+
* ``debug_verbose`` set to ``Context.all`` after importing ``from cdxcore.verbose import Context``
|
|
3598
|
+
will turn on tracing all caching operations.
|
|
3599
|
+
|
|
3447
3600
|
Returns
|
|
3448
3601
|
-------
|
|
3449
3602
|
Root : :class:`cdxcore.subdir.SubDir`
|
|
@@ -3527,7 +3680,7 @@ def _ensure_has_version( F,
|
|
|
3527
3680
|
dependencies=dependencies,
|
|
3528
3681
|
auto_class=auto_class)(F)
|
|
3529
3682
|
|
|
3530
|
-
def _qualified_name( F, name ):
|
|
3683
|
+
def _qualified_name( F, name = None ):
|
|
3531
3684
|
"""
|
|
3532
3685
|
Return qualified name including module name, robustly
|
|
3533
3686
|
"""
|
|
@@ -3546,6 +3699,36 @@ def _qualified_name( F, name ):
|
|
|
3546
3699
|
warn( f"Cannot determine module name for '{name}' of {type(F)}" )
|
|
3547
3700
|
return name
|
|
3548
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
|
+
|
|
3549
3732
|
class CacheCallable(object):
|
|
3550
3733
|
"""
|
|
3551
3734
|
Wrapper for a cached function.
|
|
@@ -3554,17 +3737,17 @@ class CacheCallable(object):
|
|
|
3554
3737
|
"""
|
|
3555
3738
|
|
|
3556
3739
|
def __init__(self,
|
|
3557
|
-
subdir
|
|
3558
|
-
version
|
|
3559
|
-
dependencies
|
|
3560
|
-
label
|
|
3561
|
-
uid
|
|
3562
|
-
name
|
|
3563
|
-
exclude_args
|
|
3564
|
-
include_args
|
|
3565
|
-
exclude_arg_types
|
|
3566
|
-
version_auto_class
|
|
3567
|
-
|
|
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"):
|
|
3568
3751
|
"""
|
|
3569
3752
|
Utility class for :dec:`cdxcore.subdir.SubDir.cache`.
|
|
3570
3753
|
|
|
@@ -3573,18 +3756,38 @@ class CacheCallable(object):
|
|
|
3573
3756
|
if not label is None and not uid is None:
|
|
3574
3757
|
error("Cannot specify both 'label' and 'uid'.")
|
|
3575
3758
|
|
|
3576
|
-
self._subdir
|
|
3577
|
-
self.
|
|
3578
|
-
self._dependencies
|
|
3579
|
-
self._label
|
|
3580
|
-
self._uid
|
|
3581
|
-
self._name
|
|
3582
|
-
self._exclude_args
|
|
3583
|
-
self._include_args
|
|
3584
|
-
self._exclude_arg_types
|
|
3585
|
-
self._version_auto_class
|
|
3586
|
-
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
|
|
3587
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
|
+
|
|
3588
3791
|
@property
|
|
3589
3792
|
def uid_or_label(self) -> Callable:
|
|
3590
3793
|
""" ID or label """
|
|
@@ -3617,6 +3820,10 @@ class CacheCallable(object):
|
|
|
3617
3820
|
def global_exclude_arg_types(self) -> list[type]:
|
|
3618
3821
|
""" Returns ``exclude_arg_types`` of the underlying :class:`cdxcore.subdir.CacheController` """
|
|
3619
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
|
|
3620
3827
|
|
|
3621
3828
|
def __call__(self, F : Callable):
|
|
3622
3829
|
"""
|
|
@@ -3668,7 +3875,7 @@ class CacheCallable(object):
|
|
|
3668
3875
|
# apply version
|
|
3669
3876
|
# this also ensures that __init__ picks up a version dependency on the class itse
|
|
3670
3877
|
# (as we forceed 'auto_class' to be true)
|
|
3671
|
-
C = _ensure_has_version( C, version=self.
|
|
3878
|
+
C = _ensure_has_version( C, version=self._input_version,
|
|
3672
3879
|
dependencies=self._dependencies,
|
|
3673
3880
|
auto_class=self._version_auto_class)
|
|
3674
3881
|
|
|
@@ -3689,7 +3896,7 @@ class CacheCallable(object):
|
|
|
3689
3896
|
# Cannot currently decorate classes.
|
|
3690
3897
|
|
|
3691
3898
|
|
|
3692
|
-
is_method = inspect.ismethod(F)
|
|
3899
|
+
is_method = inspect.ismethod(F) # for *bound* methods
|
|
3693
3900
|
if is_method:
|
|
3694
3901
|
assert not getattr(F, "__self__", None) is None, ("Method type must have __self__...?", F.__qualname__ )
|
|
3695
3902
|
elif not inspect.isfunction(F):
|
|
@@ -3752,11 +3959,12 @@ class CacheCallable(object):
|
|
|
3752
3959
|
# -------
|
|
3753
3960
|
# Decorate now or pick up existing @version
|
|
3754
3961
|
|
|
3755
|
-
F = _ensure_has_version( F, version=self.
|
|
3962
|
+
F = _ensure_has_version( F, version=self._input_version,
|
|
3756
3963
|
dependencies=self._dependencies,
|
|
3757
3964
|
auto_class=self._version_auto_class,
|
|
3758
3965
|
allow_default=is_new )
|
|
3759
|
-
|
|
3966
|
+
version = F.version.unique_id64
|
|
3967
|
+
|
|
3760
3968
|
# name
|
|
3761
3969
|
# ----
|
|
3762
3970
|
|
|
@@ -3768,8 +3976,9 @@ class CacheCallable(object):
|
|
|
3768
3976
|
exclude_types = ( self._exclude_arg_types if not self._exclude_arg_types is None else set() )\
|
|
3769
3977
|
| ( self.global_exclude_arg_types if not self.global_exclude_arg_types is None else set())
|
|
3770
3978
|
|
|
3771
|
-
def execute( *args, override_cache_mode : CacheMode = None,
|
|
3772
|
-
track_cached_files : CacheTracker = None,
|
|
3979
|
+
def execute( *args, override_cache_mode : CacheMode|None = None,
|
|
3980
|
+
track_cached_files : CacheTracker|None = None,
|
|
3981
|
+
return_cache_uid : bool = False,
|
|
3773
3982
|
**kwargs ):
|
|
3774
3983
|
"""
|
|
3775
3984
|
Cached execution of the wrapped function
|
|
@@ -3785,38 +3994,46 @@ class CacheCallable(object):
|
|
|
3785
3994
|
|
|
3786
3995
|
uid_or_label = self.uid_or_label
|
|
3787
3996
|
filename = None
|
|
3788
|
-
if
|
|
3789
|
-
#
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
try:
|
|
3793
|
-
filename = uid_or_label.format() # throws a KeyError if 'id' contains formatting information
|
|
3794
|
-
except KeyError:
|
|
3795
|
-
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()
|
|
3796
4001
|
|
|
3797
|
-
if not filename is None:
|
|
3798
|
-
# generate name with the unique string provided by the user
|
|
3799
4002
|
if not is_filename(filename):
|
|
3800
4003
|
raise ValueError(f"The unique filename '{filename}' computed for '{name}' contains invalid characters for filename. When using `uid` make sure that "+\
|
|
3801
|
-
"the returned ID is a valid filename (and unique)")
|
|
4004
|
+
"the returned ID is a valid filename (and is unique)")
|
|
3802
4005
|
label = filename
|
|
3803
|
-
filename = self.uniqueFileName( filename )
|
|
3804
4006
|
arguments = None
|
|
3805
|
-
|
|
4007
|
+
|
|
3806
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
|
+
|
|
3807
4012
|
# get dictionary of named arguments
|
|
3808
4013
|
arguments = execute.cache_info.signature.bind(*args,**kwargs)
|
|
3809
4014
|
arguments.apply_defaults()
|
|
3810
4015
|
arguments = arguments.arguments # ordered dict
|
|
3811
4016
|
|
|
4017
|
+
# delete 'cls' from argument list for class functions
|
|
3812
4018
|
if is_new:
|
|
3813
|
-
# delete 'cls' from argument list
|
|
3814
4019
|
assert len(arguments) >= 1, ("*** Internal error", F.__qualname__, is_new, arguments)
|
|
3815
4020
|
del arguments[list(arguments)[0]]
|
|
3816
|
-
|
|
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
|
|
3817
4033
|
|
|
3818
4034
|
# filter dictionary
|
|
3819
4035
|
if not self._exclude_args is None or not self._include_args is None:
|
|
4036
|
+
argus = set(arguments)
|
|
3820
4037
|
excl = set(self._exclude_args) if not self._exclude_args is None else set()
|
|
3821
4038
|
if not self._exclude_args is None:
|
|
3822
4039
|
if self._exclude_args > argus:
|
|
@@ -3830,7 +4047,7 @@ class CacheCallable(object):
|
|
|
3830
4047
|
for arg in excl:
|
|
3831
4048
|
if arg in arguments:
|
|
3832
4049
|
del arguments[arg]
|
|
3833
|
-
del excl
|
|
4050
|
+
del excl, argus
|
|
3834
4051
|
|
|
3835
4052
|
if len(exclude_types) > 0:
|
|
3836
4053
|
excl = []
|
|
@@ -3841,41 +4058,45 @@ class CacheCallable(object):
|
|
|
3841
4058
|
if arg in arguments:
|
|
3842
4059
|
del arguments[arg]
|
|
3843
4060
|
|
|
3844
|
-
# did the user provide a label or unique ID?
|
|
3845
4061
|
if uid_or_label is None:
|
|
4062
|
+
# no label or unique ID
|
|
4063
|
+
assert not self.unique
|
|
3846
4064
|
uid_or_label = name
|
|
3847
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
|
+
|
|
3848
4071
|
else:
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
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]
|
|
3862
4089
|
|
|
3863
4090
|
# call format or function
|
|
3864
4091
|
if isinstance( uid_or_label, str ):
|
|
3865
|
-
|
|
3866
|
-
uid_or_label = str.format( uid_or_label, **arguments )
|
|
3867
|
-
except KeyError as e:
|
|
3868
|
-
raise KeyError(e, f"Error while generating id for '{name}' using format string '{uid_or_label}': {e}. Available arguments: {list(arguments)}")
|
|
3869
|
-
|
|
4092
|
+
uid_or_label = str.format( uid_or_label, **fmt_arguments )
|
|
3870
4093
|
else:
|
|
3871
|
-
which = 'uid' if not self._uid is None else 'label'
|
|
3872
4094
|
try:
|
|
3873
|
-
uid_or_label = uid_or_label(**
|
|
3874
|
-
except TypeError as e:
|
|
3875
|
-
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)
|
|
3876
4096
|
except Exception as e:
|
|
3877
|
-
raise type(e)(f"Error while generating '{which}' for '{name}': attempt to call '{which}' of type {type(uid_or_label)} failed: {e}")
|
|
3878
|
-
|
|
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))}")
|
|
3879
4100
|
|
|
3880
4101
|
if self.unique:
|
|
3881
4102
|
if not is_filename(uid_or_label):
|
|
@@ -3891,7 +4112,6 @@ class CacheCallable(object):
|
|
|
3891
4112
|
# determine version, cache mode
|
|
3892
4113
|
# ------------------
|
|
3893
4114
|
|
|
3894
|
-
version_ = self._version if not self._version is None else F.version.unique_id64
|
|
3895
4115
|
cache_mode = CacheMode(override_cache_mode) if not override_cache_mode is None else self.cache_mode
|
|
3896
4116
|
del override_cache_mode
|
|
3897
4117
|
|
|
@@ -3899,8 +4119,8 @@ class CacheCallable(object):
|
|
|
3899
4119
|
# -------------------------
|
|
3900
4120
|
|
|
3901
4121
|
execute.cache_info.label = str(label) if not label is None else None
|
|
3902
|
-
execute.cache_info.filename = filename
|
|
3903
|
-
execute.cache_info.version =
|
|
4122
|
+
execute.cache_info.filename = filename # that is the unique ID for this call
|
|
4123
|
+
execute.cache_info.version = version
|
|
3904
4124
|
|
|
3905
4125
|
if self.cache_controller.keep_last_arguments:
|
|
3906
4126
|
info_arguments = OrderedDict()
|
|
@@ -3919,11 +4139,11 @@ class CacheCallable(object):
|
|
|
3919
4139
|
pass
|
|
3920
4140
|
tag = Tag()
|
|
3921
4141
|
if not is_new:
|
|
3922
|
-
r = self._subdir.read( filename, tag, version=
|
|
4142
|
+
r = self._subdir.read( filename, tag, version=version )
|
|
3923
4143
|
else:
|
|
3924
4144
|
try:
|
|
3925
4145
|
execute.__new_during_read = True
|
|
3926
|
-
r = self._subdir.read( filename, tag, version=
|
|
4146
|
+
r = self._subdir.read( filename, tag, version=version )
|
|
3927
4147
|
finally:
|
|
3928
4148
|
execute.__new_during_read = False
|
|
3929
4149
|
|
|
@@ -3932,11 +4152,13 @@ class CacheCallable(object):
|
|
|
3932
4152
|
track_cached_files += self._fullFileName(filename)
|
|
3933
4153
|
execute.cache_info.last_cached = True
|
|
3934
4154
|
if not debug_verbose is None:
|
|
3935
|
-
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)}'.")
|
|
3936
4156
|
if is_new:
|
|
3937
4157
|
assert r.__magic_cache_call_init__ is None, ("**** Internal error. __init__ should reset __magic_cache_call_init__", F.__qualname__, label)
|
|
3938
4158
|
r.__magic_cache_call_init__ = False # since we called __new__, __init__ will be called next
|
|
3939
4159
|
|
|
4160
|
+
if return_cache_uid:
|
|
4161
|
+
return filename, r
|
|
3940
4162
|
return r
|
|
3941
4163
|
|
|
3942
4164
|
r = F(*args, **kwargs)
|
|
@@ -3951,7 +4173,7 @@ class CacheCallable(object):
|
|
|
3951
4173
|
assert r.__magic_cache_call_init__ is None, ("**** Internal error. __init__ should reset __magic_cache_call_init__")
|
|
3952
4174
|
|
|
3953
4175
|
if cache_mode.write:
|
|
3954
|
-
self._subdir.write(filename,r,version=
|
|
4176
|
+
self._subdir.write(filename,r,version=version)
|
|
3955
4177
|
if not track_cached_files is None:
|
|
3956
4178
|
track_cached_files += self._subdir.full_file_name(filename)
|
|
3957
4179
|
execute.cache_info.last_cached = False
|
|
@@ -3963,9 +4185,12 @@ class CacheCallable(object):
|
|
|
3963
4185
|
|
|
3964
4186
|
if not debug_verbose is None:
|
|
3965
4187
|
if cache_mode.write:
|
|
3966
|
-
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)}'.")
|
|
3967
4189
|
else:
|
|
3968
|
-
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)}'.")
|
|
4191
|
+
|
|
4192
|
+
if return_cache_uid:
|
|
4193
|
+
return filename, r
|
|
3969
4194
|
return r
|
|
3970
4195
|
|
|
3971
4196
|
update_wrapper( wrapper=execute, wrapped=F )
|
cdxcore/uniquehash.py
CHANGED
|
@@ -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
|
cdxcore/version.py
CHANGED
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
cdxcore/__init__.py,sha256=
|
|
1
|
+
cdxcore/__init__.py,sha256=rJbADU_1rAxMjqaqHP9OBRFHI87MfbspQDkzEmjplfQ,127
|
|
2
2
|
cdxcore/config.py,sha256=RaxpAnTEyGXLiq-1O2prXFBb2sdyqB6hHDHPAFGe2k0,98472
|
|
3
3
|
cdxcore/deferred.py,sha256=41buUit1SOAnhVSrzlzM5hd2OOhnAS2QRDSWEpjXfdE,43138
|
|
4
4
|
cdxcore/dynalimits.py,sha256=TXEwa4wCdeM5epG1ceezaHwvmhnbU-oXlXpHNbOMQR8,11293
|
|
@@ -9,12 +9,12 @@ cdxcore/jcpool.py,sha256=Vw8o8S4_qTVQNIr2sRaBR_kHz_wO0zpYs0QjlKSYFz8,27280
|
|
|
9
9
|
cdxcore/npio.py,sha256=SVpKkFt6lyRogkns-oky0wEiWgVTalgB0OdaSvyWtlU,24212
|
|
10
10
|
cdxcore/npshm.py,sha256=9buYPNJoNlw69NZQ-nLF13PEWBWNx51a0gjQw5Gc24U,18368
|
|
11
11
|
cdxcore/pretty.py,sha256=FsI62rlaqRX1E-uPCSnu0M4UoCQ5Z55TDvYPnzTNO70,17220
|
|
12
|
-
cdxcore/subdir.py,sha256=
|
|
13
|
-
cdxcore/uniquehash.py,sha256=
|
|
12
|
+
cdxcore/subdir.py,sha256=ZfyihBjAj0RlB86tFozycZO7mgM6zW9uEX7S498eLZo,190340
|
|
13
|
+
cdxcore/uniquehash.py,sha256=j2pDmDPdDX4mZ8YIpZLbg-qWXQJPuR7OvZSTGnvxwL0,50827
|
|
14
14
|
cdxcore/util.py,sha256=dqCEhrDvUM4qjeUwb6n8jPfS8WC4Navcj83gDjXfRFE,39349
|
|
15
15
|
cdxcore/verbose.py,sha256=vsjGTVnAHMPg2L2RfsowWKKPjUSnQJ3F653vDTydBkI,30223
|
|
16
|
-
cdxcore/version.py,sha256=
|
|
17
|
-
cdxcore-0.1.
|
|
16
|
+
cdxcore/version.py,sha256=pmbFIZ6Egif_ppZNFJRqEZO0HBffIzkn-hkY6HpkMpU,27325
|
|
17
|
+
cdxcore-0.1.29.dist-info/licenses/LICENSE,sha256=M-cisgK9kb1bqVRJ7vrCxHcMQQfDxdY3c2YFJJWfNQg,1090
|
|
18
18
|
docs/source/conf.py,sha256=yn3LYgw3sT45mUyll-B2emVp6jg7H6KfAHOcBg_MNv4,4182
|
|
19
19
|
tests/test_config.py,sha256=N86mH3y7k3LXEmU8uPLfrmRMZ-80VhlD35nBbpLmebg,15617
|
|
20
20
|
tests/test_deferred.py,sha256=4Xsb76r-XqHKiBuHa4jbErjMWbrgHXfPwewzzY4lf9Y,7922
|
|
@@ -23,8 +23,8 @@ tests/test_jcpool.py,sha256=bcGC3UcJ7SOHVgzZ-cooEJgncLWmhutBfqH7P5qB-iw,7901
|
|
|
23
23
|
tests/test_npio.py,sha256=v-_oO7bj9obSJD4TBk4gBM8ACL62DMNjyQ-iZmvOOdw,3039
|
|
24
24
|
tests/test_npshm.py,sha256=uVivQ3zI4_v3a4qDIY-1gjf5_2pEtKM00VlC3hubUl8,3270
|
|
25
25
|
tests/test_pretty.py,sha256=pVwTBjm3XqwEf2jq5GdZvT4cDSTGqiQFBMLqmGJYuB0,11644
|
|
26
|
-
tests/test_subdir.py,sha256=
|
|
27
|
-
tests/test_uniquehash.py,sha256=
|
|
26
|
+
tests/test_subdir.py,sha256=vNRkPTiQZUubyu-vDfaERi1dLewxRMiAxgTEYS6TJZY,15825
|
|
27
|
+
tests/test_uniquehash.py,sha256=sRYhdVezNViEnpf8f0GjWfJ-fgghjEFnJCM0sUBVshM,24985
|
|
28
28
|
tests/test_util.py,sha256=mwtz3o_RiLNn808928xP4jawHmGNxzUXiatMh0zBc3o,24342
|
|
29
29
|
tests/test_verbose.py,sha256=zXheIqAVOnwML2zsCjLugjYzB_KNzU_S4Xu2CSb4o10,4723
|
|
30
30
|
tests/test_version.py,sha256=m_RODPDFlXTC1jAIczm3t1v6tXzqczDiUFFJtGvRG98,4381
|
|
@@ -34,7 +34,7 @@ tmp/npsh1.py,sha256=mNucUl2-jNmE84GlMlliB4aJ0UQ9FqdymgcY_9mLeZY,15432
|
|
|
34
34
|
tmp/sharedarray.py,sha256=dNOT1ObCc3nM3qA3OA508NcENIBnkmWMxRPCqvMVa8A,12862
|
|
35
35
|
up/git_message.py,sha256=EfSH7Pit3ZoCiRqSMwRCUN_QyuwreU4LTIyGSutBlm4,123
|
|
36
36
|
up/pip_modify_setup.py,sha256=Esaml4yA9tFsqxLhk5bWSwvKCURONjQqfyChgFV2TSY,1584
|
|
37
|
-
cdxcore-0.1.
|
|
38
|
-
cdxcore-0.1.
|
|
39
|
-
cdxcore-0.1.
|
|
40
|
-
cdxcore-0.1.
|
|
37
|
+
cdxcore-0.1.29.dist-info/METADATA,sha256=PMjpYBxi2N0UtDMGiNn8HfY8KR-U_Nk78PnW7lq7YEQ,5939
|
|
38
|
+
cdxcore-0.1.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
cdxcore-0.1.29.dist-info/top_level.txt,sha256=phNSwCyJFe7UP2YMoi8o6ykhotatlIbJHjTp9EHM51k,26
|
|
40
|
+
cdxcore-0.1.29.dist-info/RECORD,,
|
tests/test_subdir.py
CHANGED
|
@@ -25,14 +25,15 @@ import_local()
|
|
|
25
25
|
"""
|
|
26
26
|
Imports
|
|
27
27
|
"""
|
|
28
|
-
from cdxcore.subdir import SubDir, CacheMode, VersionError, VersionPresentError
|
|
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):
|
|
32
33
|
|
|
33
34
|
def test_subdir(self):
|
|
34
35
|
|
|
35
|
-
sub = SubDir("
|
|
36
|
+
sub = SubDir("?/.tmp_test_for_cdxbasics.subdir", delete_everything=True )
|
|
36
37
|
sub['y'] = 2
|
|
37
38
|
sub.write('z',3)
|
|
38
39
|
sub.write_string('l',"hallo")
|
|
@@ -193,11 +194,8 @@ class Test(unittest.TestCase):
|
|
|
193
194
|
with self.assertRaises(VersionError):
|
|
194
195
|
r = sub.read("test", None, version="2", raise_on_error=True)
|
|
195
196
|
# wrong version
|
|
196
|
-
sub.delete_everything()
|
|
197
197
|
|
|
198
198
|
def test_new(self):
|
|
199
|
-
|
|
200
|
-
|
|
201
199
|
subdir = SubDir("my_directory") # relative to current working directory
|
|
202
200
|
subdir = SubDir("./my_directory") # relative to current working directory
|
|
203
201
|
subdir = SubDir("~/my_directory") # relative to home directory
|
|
@@ -248,8 +246,6 @@ class Test(unittest.TestCase):
|
|
|
248
246
|
test_format( SubDir.GZIP, True )
|
|
249
247
|
test_format( SubDir.JSON_PICKLE )
|
|
250
248
|
|
|
251
|
-
|
|
252
|
-
|
|
253
249
|
def test_cache_mode(self):
|
|
254
250
|
|
|
255
251
|
on = CacheMode("on")
|
|
@@ -275,6 +271,146 @@ class Test(unittest.TestCase):
|
|
|
275
271
|
self.assertEqual( [ x.write for x in allc ], [True, True, False, False, True, False] )
|
|
276
272
|
self.assertEqual( [ x.delete for x in allc ], [False, False, False, True, True, False ] )
|
|
277
273
|
self.assertEqual( [ x.del_incomp for x in allc ], [True, False, False, True, True, False ] )
|
|
274
|
+
|
|
275
|
+
class A(object):
|
|
276
|
+
def __init__(self, x):
|
|
277
|
+
self.x = x
|
|
278
|
+
class B(object):
|
|
279
|
+
def __init__(self, x):
|
|
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
|
|
290
|
+
|
|
291
|
+
class Test(unittest.TestCase):
|
|
292
|
+
|
|
293
|
+
def test_cache( self ):
|
|
294
|
+
|
|
295
|
+
sub = VersionedCacheRoot("?/subdir_cache_test", exclude_arg_types=[A] )
|
|
296
|
+
|
|
297
|
+
@sub.cache("1.0")
|
|
298
|
+
def f(x):
|
|
299
|
+
return x
|
|
300
|
+
|
|
301
|
+
_ = f(1)
|
|
302
|
+
_ = f(1)
|
|
303
|
+
self.assertTrue( f.cache_info.last_cached )
|
|
304
|
+
|
|
305
|
+
@sub.cache("1.0")
|
|
306
|
+
def g(x):
|
|
307
|
+
return x
|
|
308
|
+
|
|
309
|
+
# a new B object each time -> no caching
|
|
310
|
+
_ = f(B(1))
|
|
311
|
+
_ = f(B(2))
|
|
312
|
+
self.assertFalse( f.cache_info.last_cached )
|
|
313
|
+
|
|
314
|
+
# same B object each time -> no caching
|
|
315
|
+
_ = f(B(1))
|
|
316
|
+
_ = f(B(1))
|
|
317
|
+
self.assertTrue( f.cache_info.last_cached )
|
|
318
|
+
|
|
319
|
+
# a new A object each time -> caching, as we ignore 'A' types
|
|
320
|
+
_ = f(A(1))
|
|
321
|
+
_ = f(A(2))
|
|
322
|
+
self.assertTrue( f.cache_info.last_cached )
|
|
323
|
+
|
|
324
|
+
@sub.cache("1.0", label=lambda x: f"f({x})")
|
|
325
|
+
def f(x):
|
|
326
|
+
return x
|
|
327
|
+
|
|
328
|
+
_ = f(1)
|
|
329
|
+
self.assertEqual( f.cache_info.filename[:5], "f(1) " )
|
|
330
|
+
uid, _ = f(1, return_cache_uid=True)
|
|
331
|
+
self.assertEqual( uid[:5], "f(1) " )
|
|
332
|
+
|
|
333
|
+
@sub.cache("1.0", uid=lambda x: f"f({x})")
|
|
334
|
+
def f(x):
|
|
335
|
+
return x
|
|
336
|
+
|
|
337
|
+
_ = f(1)
|
|
338
|
+
self.assertEqual( f.cache_info.filename, "f(1)" )
|
|
339
|
+
uid, _ = f(1, return_cache_uid=True)
|
|
340
|
+
self.assertEqual( uid, "f(1)" )
|
|
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
|
+
|
|
413
|
+
|
|
278
414
|
if __name__ == '__main__':
|
|
279
415
|
unittest.main()
|
|
280
416
|
|
tests/test_uniquehash.py
CHANGED
|
@@ -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
|