pickle-debug 1.2__tar.gz → 2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pickle_debug
3
- Version: 1.2
3
+ Version: 2.1
4
4
  Summary: Store expensive objects from debugging
5
5
  Author: Gerard
6
6
  Author-email: gweatherby@uchc.edu
@@ -59,18 +59,49 @@ slow.invoke = pickle_debug.cached(slow.invoke) # one instance
59
59
  Slow.invoke = pickle_debug.cached(Slow.invoke) # all instances
60
60
  ```
61
61
 
62
+ The decorator also works inside a class body. For a classmethod, put `@cached` below `@classmethod`:
63
+
64
+ ```python
65
+ class Slow:
66
+ @pickle_debug.cached
67
+ def invoke(self, x):
68
+ ...
69
+
70
+ @classmethod
71
+ @pickle_debug.cached
72
+ def build(cls, x):
73
+ ...
74
+ ```
75
+
76
+ A wrapper may be needed to cache some functions, e.g. a method returning a generator:
77
+
78
+ ```python
79
+ def something(x,y,z)->Generator:
80
+
81
+ @pickle_debug.cached
82
+ def cache_something(x,y,z)->list:
83
+ return list(something(x,y,z))
84
+ ```
85
+
86
+ When a cached function is called as a method, `self` (or `cls`) is keyed by its class's name
87
+ (`module.QualifiedName`), not its current value. All instances of a class share cache entries, and the
88
+ instance does not need to be picklable. Subclasses get their own entries. Static methods are keyed like
89
+ plain functions.
90
+
62
91
  ## API
63
92
 
64
- ### `enable(directory)`
93
+ ### `enable(directory: str | Path | None)`
65
94
  Turn caching on, storing results in `directory` (a `str` or `Path`). The directory is created if
66
95
  needed and checked for writability. Raises `OSError` if it cannot be created or written.
96
+ enable(None) disables caching.
67
97
 
68
98
  ### `cached(func)`
69
99
  Decorator. Returns a wrapper (using `functools.wraps`) that:
70
100
 
71
101
  1. calls `func` directly if caching is not enabled;
72
102
  2. otherwise builds a key from `func.__module__`, `func.__qualname__`, the positional arguments and
73
- the sorted keyword arguments, and hashes it with SHA-256;
103
+ the sorted keyword arguments, and hashes it with SHA-256. If `func` is called as a method, the
104
+ first argument (`self` or `cls`) is replaced by its class's name (see [Methods](#methods));
74
105
  3. returns the pickled result from `<directory>/<hash>.pickle` if present;
75
106
  4. otherwise calls `func`, writes the result to the cache, and returns it.
76
107
 
@@ -101,13 +132,15 @@ wrong place. It does nothing if caching is not enabled or the directory does not
101
132
  [Unpicklable attributes](#unpicklable-attributes)). The arguments are pickled to build the key, and the
102
133
  result is pickled to store it. Because dropped attributes are not part of the key, two arguments that
103
134
  differ only in unpicklable attributes share a cache entry.
135
+ - **Wrappers may be required** for methods that return generators.
104
136
  - **The key is the pickled arguments.** Equal arguments that pickle differently (for example sets with a
105
137
  different iteration order) may miss the cache. Objects whose pickle changes between calls never hit it.
106
138
  - **The key does not include the function's code or any files it reads.** If you change the function or
107
139
  its input data, call `reset()` or use a new cache directory, or you will get stale results.
108
- - **Bound methods and `self`.** A wrapped bound method (`slow.invoke`) does not include `self` in the key,
109
- so all instances share entries. A wrapped class function (`Slow.invoke`) includes `self`, so the
110
- instance must be picklable and its state is part of the key.
140
+ - **Instance state is not part of the key.** Methods are keyed by the class of `self` or `cls`, not by its
141
+ value, so all instances of a class share entries. If a method's result depends on instance attributes,
142
+ instances with different state will get each other's cached results. Pass that state as an argument
143
+ instead, or call `reset()` between runs.
111
144
  - This is a debugging aid. Do not use it as a production cache. Cached files are loaded with `pickle`,
112
145
  so only use a directory you trust.
113
146
 
@@ -47,18 +47,49 @@ slow.invoke = pickle_debug.cached(slow.invoke) # one instance
47
47
  Slow.invoke = pickle_debug.cached(Slow.invoke) # all instances
48
48
  ```
49
49
 
50
+ The decorator also works inside a class body. For a classmethod, put `@cached` below `@classmethod`:
51
+
52
+ ```python
53
+ class Slow:
54
+ @pickle_debug.cached
55
+ def invoke(self, x):
56
+ ...
57
+
58
+ @classmethod
59
+ @pickle_debug.cached
60
+ def build(cls, x):
61
+ ...
62
+ ```
63
+
64
+ A wrapper may be needed to cache some functions, e.g. a method returning a generator:
65
+
66
+ ```python
67
+ def something(x,y,z)->Generator:
68
+
69
+ @pickle_debug.cached
70
+ def cache_something(x,y,z)->list:
71
+ return list(something(x,y,z))
72
+ ```
73
+
74
+ When a cached function is called as a method, `self` (or `cls`) is keyed by its class's name
75
+ (`module.QualifiedName`), not its current value. All instances of a class share cache entries, and the
76
+ instance does not need to be picklable. Subclasses get their own entries. Static methods are keyed like
77
+ plain functions.
78
+
50
79
  ## API
51
80
 
52
- ### `enable(directory)`
81
+ ### `enable(directory: str | Path | None)`
53
82
  Turn caching on, storing results in `directory` (a `str` or `Path`). The directory is created if
54
83
  needed and checked for writability. Raises `OSError` if it cannot be created or written.
84
+ enable(None) disables caching.
55
85
 
56
86
  ### `cached(func)`
57
87
  Decorator. Returns a wrapper (using `functools.wraps`) that:
58
88
 
59
89
  1. calls `func` directly if caching is not enabled;
60
90
  2. otherwise builds a key from `func.__module__`, `func.__qualname__`, the positional arguments and
61
- the sorted keyword arguments, and hashes it with SHA-256;
91
+ the sorted keyword arguments, and hashes it with SHA-256. If `func` is called as a method, the
92
+ first argument (`self` or `cls`) is replaced by its class's name (see [Methods](#methods));
62
93
  3. returns the pickled result from `<directory>/<hash>.pickle` if present;
63
94
  4. otherwise calls `func`, writes the result to the cache, and returns it.
64
95
 
@@ -89,13 +120,15 @@ wrong place. It does nothing if caching is not enabled or the directory does not
89
120
  [Unpicklable attributes](#unpicklable-attributes)). The arguments are pickled to build the key, and the
90
121
  result is pickled to store it. Because dropped attributes are not part of the key, two arguments that
91
122
  differ only in unpicklable attributes share a cache entry.
123
+ - **Wrappers may be required** for methods that return generators.
92
124
  - **The key is the pickled arguments.** Equal arguments that pickle differently (for example sets with a
93
125
  different iteration order) may miss the cache. Objects whose pickle changes between calls never hit it.
94
126
  - **The key does not include the function's code or any files it reads.** If you change the function or
95
127
  its input data, call `reset()` or use a new cache directory, or you will get stale results.
96
- - **Bound methods and `self`.** A wrapped bound method (`slow.invoke`) does not include `self` in the key,
97
- so all instances share entries. A wrapped class function (`Slow.invoke`) includes `self`, so the
98
- instance must be picklable and its state is part of the key.
128
+ - **Instance state is not part of the key.** Methods are keyed by the class of `self` or `cls`, not by its
129
+ value, so all instances of a class share entries. If a method's result depends on instance attributes,
130
+ instances with different state will get each other's cached results. Pass that state as an argument
131
+ instead, or call `reset()` between runs.
99
132
  - This is a debugging aid. Do not use it as a production cache. Cached files are loaded with `pickle`,
100
133
  so only use a directory you trust.
101
134
 
@@ -2,7 +2,7 @@
2
2
  name = "pickle_debug"
3
3
  description = "Store expensive objects from debugging"
4
4
  license = {text = "MIT" }
5
- version = "1.2"
5
+ version = "2.1"
6
6
  dependencies = []
7
7
  requires-python= ">= 3.12"
8
8
  readme = "README.md"
@@ -18,16 +18,19 @@ _CACHE_DIRECTORY: Path | None = None
18
18
  _SUFFIX = '.pickle'
19
19
 
20
20
 
21
- def enable(directory: Path | str) -> None:
21
+ def enable(directory: Path | str|None) -> None:
22
22
  """Enable caching in specified directory. Raises OSError if directory can't be created or written"""
23
23
  global _CACHE_DIRECTORY
24
- directory = Path(directory)
25
- directory.mkdir(parents=True, exist_ok=True)
26
- # verify we can create and write files
27
- with tempfile.TemporaryFile(dir=directory) as f:
28
- f.write(b'test')
29
- _CACHE_DIRECTORY = directory
30
- pickle_debug_logger.debug(f"caching to {directory}")
24
+ if directory is not None:
25
+ directory = Path(directory)
26
+ directory.mkdir(parents=True, exist_ok=True)
27
+ # verify we can create and write files
28
+ with tempfile.TemporaryFile(dir=directory) as f:
29
+ f.write(b'test')
30
+ _CACHE_DIRECTORY = directory
31
+ pickle_debug_logger.debug(f"caching to {directory}")
32
+ else:
33
+ _CACHE_DIRECTORY = None
31
34
 
32
35
 
33
36
  def reset() -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pickle_debug
3
- Version: 1.2
3
+ Version: 2.1
4
4
  Summary: Store expensive objects from debugging
5
5
  Author: Gerard
6
6
  Author-email: gweatherby@uchc.edu
@@ -59,18 +59,49 @@ slow.invoke = pickle_debug.cached(slow.invoke) # one instance
59
59
  Slow.invoke = pickle_debug.cached(Slow.invoke) # all instances
60
60
  ```
61
61
 
62
+ The decorator also works inside a class body. For a classmethod, put `@cached` below `@classmethod`:
63
+
64
+ ```python
65
+ class Slow:
66
+ @pickle_debug.cached
67
+ def invoke(self, x):
68
+ ...
69
+
70
+ @classmethod
71
+ @pickle_debug.cached
72
+ def build(cls, x):
73
+ ...
74
+ ```
75
+
76
+ A wrapper may be needed to cache some functions, e.g. a method returning a generator:
77
+
78
+ ```python
79
+ def something(x,y,z)->Generator:
80
+
81
+ @pickle_debug.cached
82
+ def cache_something(x,y,z)->list:
83
+ return list(something(x,y,z))
84
+ ```
85
+
86
+ When a cached function is called as a method, `self` (or `cls`) is keyed by its class's name
87
+ (`module.QualifiedName`), not its current value. All instances of a class share cache entries, and the
88
+ instance does not need to be picklable. Subclasses get their own entries. Static methods are keyed like
89
+ plain functions.
90
+
62
91
  ## API
63
92
 
64
- ### `enable(directory)`
93
+ ### `enable(directory: str | Path | None)`
65
94
  Turn caching on, storing results in `directory` (a `str` or `Path`). The directory is created if
66
95
  needed and checked for writability. Raises `OSError` if it cannot be created or written.
96
+ enable(None) disables caching.
67
97
 
68
98
  ### `cached(func)`
69
99
  Decorator. Returns a wrapper (using `functools.wraps`) that:
70
100
 
71
101
  1. calls `func` directly if caching is not enabled;
72
102
  2. otherwise builds a key from `func.__module__`, `func.__qualname__`, the positional arguments and
73
- the sorted keyword arguments, and hashes it with SHA-256;
103
+ the sorted keyword arguments, and hashes it with SHA-256. If `func` is called as a method, the
104
+ first argument (`self` or `cls`) is replaced by its class's name (see [Methods](#methods));
74
105
  3. returns the pickled result from `<directory>/<hash>.pickle` if present;
75
106
  4. otherwise calls `func`, writes the result to the cache, and returns it.
76
107
 
@@ -101,13 +132,15 @@ wrong place. It does nothing if caching is not enabled or the directory does not
101
132
  [Unpicklable attributes](#unpicklable-attributes)). The arguments are pickled to build the key, and the
102
133
  result is pickled to store it. Because dropped attributes are not part of the key, two arguments that
103
134
  differ only in unpicklable attributes share a cache entry.
135
+ - **Wrappers may be required** for methods that return generators.
104
136
  - **The key is the pickled arguments.** Equal arguments that pickle differently (for example sets with a
105
137
  different iteration order) may miss the cache. Objects whose pickle changes between calls never hit it.
106
138
  - **The key does not include the function's code or any files it reads.** If you change the function or
107
139
  its input data, call `reset()` or use a new cache directory, or you will get stale results.
108
- - **Bound methods and `self`.** A wrapped bound method (`slow.invoke`) does not include `self` in the key,
109
- so all instances share entries. A wrapped class function (`Slow.invoke`) includes `self`, so the
110
- instance must be picklable and its state is part of the key.
140
+ - **Instance state is not part of the key.** Methods are keyed by the class of `self` or `cls`, not by its
141
+ value, so all instances of a class share entries. If a method's result depends on instance attributes,
142
+ instances with different state will get each other's cached results. Pass that state as an argument
143
+ instead, or call `reset()` between runs.
111
144
  - This is a debugging aid. Do not use it as a production cache. Cached files are loaded with `pickle`,
112
145
  so only use a directory you trust.
113
146
 
@@ -6,8 +6,7 @@ src/pickle_debug.egg-info/PKG-INFO
6
6
  src/pickle_debug.egg-info/SOURCES.txt
7
7
  src/pickle_debug.egg-info/dependency_links.txt
8
8
  src/pickle_debug.egg-info/top_level.txt
9
+ src/test/ntest.py
9
10
  src/test/patch_test.py
10
11
  src/test/pd_test.py
11
- src/test/record.py
12
- src/test/ttest.py
13
- src/test/unpicklable_test.py
12
+ src/test/ttest.py
@@ -0,0 +1,15 @@
1
+ import time
2
+
3
+ import pickle_debug
4
+
5
+
6
+ @pickle_debug.cached
7
+ def f(x, y=1):
8
+ print(f"call {x} {y}")
9
+ time.sleep(1.5)
10
+ return x + y
11
+
12
+
13
+ pickle_debug.enable(None)
14
+ print(f(1, 2))
15
+ print(f(1, 2))
@@ -1,11 +0,0 @@
1
- import pickle_debug
2
-
3
- def sum(x,y):
4
- print(f"{x} + {y}")
5
- return x + y
6
-
7
- pickle_debug.enable("/tmp/sum")
8
- ans = pickle_debug.record("sum",sum,3,4)
9
- print(ans)
10
- ans = pickle_debug.record("sum",sum,3,4)
11
- print(ans)
@@ -1,46 +0,0 @@
1
- import pathlib
2
- import tempfile
3
- import threading
4
-
5
- from pickle_debug import cached, enable
6
-
7
-
8
- class Holder:
9
- def __init__(self, value):
10
- self.value = value
11
- self.lock = threading.Lock()
12
- self.inner = Inner()
13
- self.me = self
14
-
15
-
16
- class Inner:
17
- def __init__(self):
18
- self.ok = 'inner'
19
- self.callback = lambda: None
20
-
21
-
22
- class Slotted:
23
- __slots__ = ('a', 'lock')
24
-
25
- def __init__(self):
26
- self.a = 1
27
- self.lock = threading.Lock()
28
-
29
-
30
- calls = []
31
-
32
-
33
- @cached
34
- def make(x):
35
- calls.append(x)
36
- return Holder(x), Slotted()
37
-
38
-
39
- enable(pathlib.Path(tempfile.mkdtemp()) / 'cache')
40
- make(5)
41
- h, s = make(5)
42
- assert calls == [5], calls
43
- assert h.value == 5 and not hasattr(h, 'lock') and h.me is h
44
- assert h.inner.ok == 'inner' and not hasattr(h.inner, 'callback')
45
- assert s.a == 1 and not hasattr(s, 'lock')
46
- print('ok')
File without changes
File without changes
File without changes