paramtrack 0.2.0__tar.gz → 0.3.0__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: paramtrack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Remembers the arguments of each method's last call, defaults included.
|
|
5
5
|
Author: Alessio Popovic
|
|
6
6
|
Author-email: Alessio Popovic <alessiopopovic@gmail.com>
|
|
@@ -8,10 +8,15 @@ License-Expression: MIT
|
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Requires-Python: >=3.9
|
|
10
10
|
Project-URL: Repository, https://github.com/AlessioPop/paramtrack
|
|
11
|
+
Project-URL: Documentation, https://paramtrack.readthedocs.io
|
|
11
12
|
Description-Content-Type: text/markdown
|
|
12
13
|
|
|
13
14
|
# paramtrack
|
|
14
15
|
|
|
16
|
+
[](https://pypi.org/project/paramtrack/)
|
|
17
|
+
[](https://github.com/AlessioPop/paramtrack/actions/workflows/tests.yml)
|
|
18
|
+
[](https://paramtrack.readthedocs.io/en/latest/)
|
|
19
|
+
|
|
15
20
|
Remembers the arguments of each method's last call, defaults included.
|
|
16
21
|
|
|
17
22
|
## Install
|
|
@@ -74,7 +79,7 @@ This works through chains of `**kwargs` and for calls like `self.method(**kwargs
|
|
|
74
79
|
|
|
75
80
|
### Functions
|
|
76
81
|
|
|
77
|
-
`track_function` does the same for plain functions and stores the arguments as `function.parameters
|
|
82
|
+
`track_function` does the same for plain functions and stores the arguments as `function.parameters`. Before the first call, it shows the defaults, with `?` for arguments that have none:
|
|
78
83
|
|
|
79
84
|
```python
|
|
80
85
|
from paramtrack import track_function
|
|
@@ -83,7 +88,28 @@ from paramtrack import track_function
|
|
|
83
88
|
def observe(target, exposure=30, filter="V"):
|
|
84
89
|
...
|
|
85
90
|
|
|
86
|
-
observe("Vega")
|
|
87
|
-
|
|
88
91
|
print(observe.parameters)
|
|
89
92
|
```
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
observe
|
|
96
|
+
├─ target = ?
|
|
97
|
+
├─ exposure = 30
|
|
98
|
+
└─ filter = 'V'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
After a call such as `observe("Vega")`, the values of that call are shown instead.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
Run the tests:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv run pytest
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Build the docs into `docs/_build/html`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv run --with-requirements docs/requirements.txt sphinx-build docs docs/_build/html
|
|
115
|
+
```
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# paramtrack
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/paramtrack/)
|
|
4
|
+
[](https://github.com/AlessioPop/paramtrack/actions/workflows/tests.yml)
|
|
5
|
+
[](https://paramtrack.readthedocs.io/en/latest/)
|
|
6
|
+
|
|
3
7
|
Remembers the arguments of each method's last call, defaults included.
|
|
4
8
|
|
|
5
9
|
## Install
|
|
@@ -62,7 +66,7 @@ This works through chains of `**kwargs` and for calls like `self.method(**kwargs
|
|
|
62
66
|
|
|
63
67
|
### Functions
|
|
64
68
|
|
|
65
|
-
`track_function` does the same for plain functions and stores the arguments as `function.parameters
|
|
69
|
+
`track_function` does the same for plain functions and stores the arguments as `function.parameters`. Before the first call, it shows the defaults, with `?` for arguments that have none:
|
|
66
70
|
|
|
67
71
|
```python
|
|
68
72
|
from paramtrack import track_function
|
|
@@ -71,7 +75,28 @@ from paramtrack import track_function
|
|
|
71
75
|
def observe(target, exposure=30, filter="V"):
|
|
72
76
|
...
|
|
73
77
|
|
|
74
|
-
observe("Vega")
|
|
75
|
-
|
|
76
78
|
print(observe.parameters)
|
|
77
79
|
```
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
observe
|
|
83
|
+
├─ target = ?
|
|
84
|
+
├─ exposure = 30
|
|
85
|
+
└─ filter = 'V'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
After a call such as `observe("Vega")`, the values of that call are shown instead.
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
Run the tests:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv run pytest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Build the docs into `docs/_build/html`:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv run --with-requirements docs/requirements.txt sphinx-build docs docs/_build/html
|
|
102
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "paramtrack"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Remembers the arguments of each method's last call, defaults included."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -13,6 +13,10 @@ dependencies = []
|
|
|
13
13
|
|
|
14
14
|
[project.urls]
|
|
15
15
|
Repository = "https://github.com/AlessioPop/paramtrack"
|
|
16
|
+
Documentation = "https://paramtrack.readthedocs.io"
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = ["pytest"]
|
|
16
20
|
|
|
17
21
|
[build-system]
|
|
18
22
|
requires = ["uv_build>=0.11.28,<0.12.0"]
|
|
@@ -17,9 +17,29 @@ def tree(arguments):
|
|
|
17
17
|
return [f"├─ {line}" for line in lines[:-1]] + [f"└─ {line}" for line in lines[-1:]]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
# Stands in for an argument without a default until a call gives it a value.
|
|
21
|
+
class Unset:
|
|
22
|
+
def __repr__(self):
|
|
23
|
+
return "?"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
UNSET = Unset()
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
class Parameters(dict):
|
|
30
|
+
def __init__(self):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.uncalled = {} # functions not called yet, as name -> function giving their defaults
|
|
33
|
+
|
|
34
|
+
# Recompute the entries of functions not called yet: what their **kwargs are passed on to
|
|
35
|
+
# may have been defined after them.
|
|
36
|
+
def refresh(self):
|
|
37
|
+
for name, defaults in self.uncalled.items():
|
|
38
|
+
self[name] = defaults()
|
|
39
|
+
|
|
21
40
|
# Text version, used by print() and the terminal.
|
|
22
41
|
def __repr__(self):
|
|
42
|
+
self.refresh()
|
|
23
43
|
blocks = []
|
|
24
44
|
for name, arguments in self.items():
|
|
25
45
|
blocks.append("\n".join([f"\033[1m{name}\033[0m", *tree(arguments)])) # \033[1m = bold
|
|
@@ -27,6 +47,7 @@ class Parameters(dict):
|
|
|
27
47
|
|
|
28
48
|
# Clickable version, used by Jupyter; click a method name to unfold it.
|
|
29
49
|
def _repr_html_(self):
|
|
50
|
+
self.refresh()
|
|
30
51
|
html = ""
|
|
31
52
|
for name, arguments in self.items():
|
|
32
53
|
lines = escape("\n".join(tree(arguments)))
|
|
@@ -64,7 +85,8 @@ def resolve(node, namespace):
|
|
|
64
85
|
raise LookupError(ast.dump(node))
|
|
65
86
|
|
|
66
87
|
|
|
67
|
-
# The parameters that func's **kwargs end up in: the passed value where given, the default otherwise
|
|
88
|
+
# The parameters that func's **kwargs end up in: the passed value where given, the default otherwise,
|
|
89
|
+
# and UNSET for those without one.
|
|
68
90
|
# local holds func's own arguments so that self.method(**kwargs) resolves. Follows chains of **kwargs.
|
|
69
91
|
def forwarded(func, local, kwargs, seen=()):
|
|
70
92
|
seen = (*seen, func)
|
|
@@ -84,8 +106,8 @@ def forwarded(func, local, kwargs, seen=()):
|
|
|
84
106
|
continue
|
|
85
107
|
if p.name in rest:
|
|
86
108
|
result.setdefault(p.name, rest.pop(p.name))
|
|
87
|
-
|
|
88
|
-
result.setdefault(p.name, p.default)
|
|
109
|
+
else:
|
|
110
|
+
result.setdefault(p.name, UNSET if p.default is p.empty else p.default)
|
|
89
111
|
inner = inspect.unwrap(getattr(callee, "__func__", callee))
|
|
90
112
|
if parameters and parameters[-1].kind is VAR_KEYWORD and inspect.isfunction(inner) and inner not in seen:
|
|
91
113
|
inner_local = {next(iter(inspect.signature(inner).parameters)): callee.__self__} if inspect.ismethod(callee) else {}
|
|
@@ -96,6 +118,19 @@ def forwarded(func, local, kwargs, seen=()):
|
|
|
96
118
|
return result
|
|
97
119
|
|
|
98
120
|
|
|
121
|
+
# What is stored for one call: {name: repr(value)}, without self.
|
|
122
|
+
def describe(function, signature, arguments):
|
|
123
|
+
result = {}
|
|
124
|
+
for name, value in arguments.items():
|
|
125
|
+
# Instead of one dict, list what **kwargs is passed on to, defaults included.
|
|
126
|
+
if signature.parameters[name].kind is VAR_KEYWORD:
|
|
127
|
+
for key, val in forwarded(inspect.unwrap(function), arguments, value).items():
|
|
128
|
+
result.setdefault(key, val)
|
|
129
|
+
elif name != "self":
|
|
130
|
+
result[name] = value
|
|
131
|
+
return {k: repr(v) for k, v in result.items()}
|
|
132
|
+
|
|
133
|
+
|
|
99
134
|
def track(function, parameters):
|
|
100
135
|
signature = inspect.signature(function)
|
|
101
136
|
|
|
@@ -103,21 +138,41 @@ def track(function, parameters):
|
|
|
103
138
|
def wrapper(*args, **kwargs):
|
|
104
139
|
bound = signature.bind(*args, **kwargs)
|
|
105
140
|
bound.apply_defaults()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# Instead of one dict, list what **kwargs is passed on to, defaults included.
|
|
109
|
-
if signature.parameters[name].kind is VAR_KEYWORD:
|
|
110
|
-
for key, val in forwarded(inspect.unwrap(function), bound.arguments, value).items():
|
|
111
|
-
arguments.setdefault(key, val)
|
|
112
|
-
elif name != "self":
|
|
113
|
-
arguments[name] = value
|
|
114
|
-
parameters[function.__name__] = {k: repr(v) for k, v in arguments.items()}
|
|
141
|
+
parameters.uncalled.pop(function.__name__, None)
|
|
142
|
+
parameters[function.__name__] = describe(function, signature, bound.arguments)
|
|
115
143
|
return function(*args, **kwargs)
|
|
116
144
|
return wrapper
|
|
117
145
|
|
|
118
146
|
|
|
119
|
-
# Class decorator: track every method and store the arguments as cls.parameters.
|
|
120
147
|
def track_class(cls):
|
|
148
|
+
"""Track the arguments of every method of a class.
|
|
149
|
+
|
|
150
|
+
Each call stores the method's arguments, defaults included, in ``cls.parameters``
|
|
151
|
+
under the method's name, replacing those of the previous call. Keywords that a
|
|
152
|
+
method passes on with ``**kwargs`` are listed together with the parameters and
|
|
153
|
+
defaults of the function receiving them.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
cls : type
|
|
158
|
+
The class to track.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
type
|
|
163
|
+
The same class, with its methods wrapped and a ``parameters`` attribute added.
|
|
164
|
+
|
|
165
|
+
Examples
|
|
166
|
+
--------
|
|
167
|
+
>>> @track_class
|
|
168
|
+
... class Telescope:
|
|
169
|
+
... def observe(self, target, exposure=30):
|
|
170
|
+
... ...
|
|
171
|
+
>>> telescope = Telescope()
|
|
172
|
+
>>> telescope.observe("Vega")
|
|
173
|
+
>>> telescope.parameters["observe"]
|
|
174
|
+
{'target': "'Vega'", 'exposure': '30'}
|
|
175
|
+
"""
|
|
121
176
|
cls.parameters = Parameters()
|
|
122
177
|
for name, method in list(vars(cls).items()):
|
|
123
178
|
if inspect.isfunction(method):
|
|
@@ -125,9 +180,43 @@ def track_class(cls):
|
|
|
125
180
|
return cls
|
|
126
181
|
|
|
127
182
|
|
|
128
|
-
# Function decorator: track the function and store its arguments as function.parameters.
|
|
129
183
|
def track_function(function):
|
|
184
|
+
"""Track the arguments of a function.
|
|
185
|
+
|
|
186
|
+
Each call stores the arguments, defaults included, in ``function.parameters``,
|
|
187
|
+
replacing those of the previous call. Before the first call, ``function.parameters``
|
|
188
|
+
shows the defaults, with ``?`` for arguments that have none. Keywords passed on with
|
|
189
|
+
``**kwargs`` are listed as in :func:`track_class`.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
function : callable
|
|
194
|
+
The function to track.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
callable
|
|
199
|
+
A wrapper around the function with a ``parameters`` attribute.
|
|
200
|
+
|
|
201
|
+
Examples
|
|
202
|
+
--------
|
|
203
|
+
>>> @track_function
|
|
204
|
+
... def observe(target, exposure=30):
|
|
205
|
+
... ...
|
|
206
|
+
>>> observe.parameters["observe"]
|
|
207
|
+
{'target': '?', 'exposure': '30'}
|
|
208
|
+
>>> observe("Vega")
|
|
209
|
+
>>> observe.parameters["observe"]
|
|
210
|
+
{'target': "'Vega'", 'exposure': '30'}
|
|
211
|
+
"""
|
|
130
212
|
parameters = Parameters()
|
|
131
213
|
wrapper = track(function, parameters)
|
|
132
214
|
wrapper.parameters = parameters
|
|
215
|
+
# Before the first call: the defaults, and UNSET for arguments without one.
|
|
216
|
+
signature = inspect.signature(function)
|
|
217
|
+
bound = signature.bind_partial()
|
|
218
|
+
bound.apply_defaults()
|
|
219
|
+
arguments = {name: bound.arguments.get(name, UNSET) for name in signature.parameters}
|
|
220
|
+
parameters.uncalled[function.__name__] = lambda: describe(function, signature, arguments)
|
|
221
|
+
parameters.refresh()
|
|
133
222
|
return wrapper
|
|
File without changes
|