scistack-db 0.1.9.dev0__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.
- scidb/__init__.py +144 -0
- scidb/across_variants.py +97 -0
- scidb/artifact_stamp.py +301 -0
- scidb/colname.py +57 -0
- scidb/column_selection.py +166 -0
- scidb/constant.py +295 -0
- scidb/csv_export.py +272 -0
- scidb/database.py +4345 -0
- scidb/discover.py +449 -0
- scidb/each_of.py +26 -0
- scidb/exceptions.py +81 -0
- scidb/exclusions.py +405 -0
- scidb/filters.py +1225 -0
- scidb/fixed.py +55 -0
- scidb/foreach.py +5009 -0
- scidb/foreach_config.py +197 -0
- scidb/hashing.py +9 -0
- scidb/inspect/__init__.py +63 -0
- scidb/inspect/api.py +1088 -0
- scidb/inspect/cli.py +819 -0
- scidb/inspect/graph.py +422 -0
- scidb/inspect/mutate.py +157 -0
- scidb/inspect/pick.py +81 -0
- scidb/inspect/render.py +732 -0
- scidb/inspect/report.py +559 -0
- scidb/lineage_save.py +30 -0
- scidb/log.py +14 -0
- scidb/merge.py +94 -0
- scidb/paths.py +9 -0
- scidb/pipeline.py +1301 -0
- scidb/provenance.py +412 -0
- scidb/provenance_query.py +1102 -0
- scidb/provenance_save.py +737 -0
- scidb/query.py +132 -0
- scidb/state.py +659 -0
- scidb/variable.py +815 -0
- scidb/variables/__init__.py +1 -0
- scidb/variant.py +153 -0
- scistack_db-0.1.9.dev0.dist-info/METADATA +128 -0
- scistack_db-0.1.9.dev0.dist-info/RECORD +42 -0
- scistack_db-0.1.9.dev0.dist-info/WHEEL +4 -0
- scistack_db-0.1.9.dev0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Column selection wrapper for variable types in for_each (DB-backed)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ColumnSelection:
|
|
7
|
+
"""
|
|
8
|
+
Wraps a variable class with column selection for use in for_each() inputs.
|
|
9
|
+
|
|
10
|
+
Created automatically by BaseVariable.__class_getitem__ when using bracket
|
|
11
|
+
syntax:
|
|
12
|
+
|
|
13
|
+
MyVar["col_name"] # single column -> numpy array
|
|
14
|
+
MyVar[["col_a", "col_b"]] # multiple columns -> DataFrame subset
|
|
15
|
+
|
|
16
|
+
After loading, only the specified columns are extracted from the loaded
|
|
17
|
+
DataFrame. Single column selection returns a numpy array; multiple columns
|
|
18
|
+
return a DataFrame subset.
|
|
19
|
+
|
|
20
|
+
When ``iterate=True`` (constructed via ``MyVar.for_columns(...)``) the
|
|
21
|
+
selection means "run fn once per column and reassemble the per-column
|
|
22
|
+
results into a single wide output variable" rather than "pass the columns
|
|
23
|
+
as one argument." An **empty** ``columns`` (``[]``, the default) means "all
|
|
24
|
+
data columns", resolved at for_each time. ``None`` is accepted as an alias
|
|
25
|
+
for the empty/all sentinel for backward compatibility.
|
|
26
|
+
|
|
27
|
+
For standalone DataFrame usage, see scifor.ColumnSelection.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, var_type: type, columns: "list[str] | None" = None, iterate: bool = False
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Args:
|
|
35
|
+
var_type: The variable class to load.
|
|
36
|
+
columns: List of column names to extract after loading. An empty
|
|
37
|
+
list ``[]`` (the default) means "all data columns", resolved at
|
|
38
|
+
for_each time (only meaningful with ``iterate=True``). ``None``
|
|
39
|
+
is accepted as an alias for ``[]``.
|
|
40
|
+
iterate: If True, iterate over the columns (one fn call each) and
|
|
41
|
+
reassemble into one wide output; if False (default), pass the
|
|
42
|
+
column(s) as a single argument.
|
|
43
|
+
"""
|
|
44
|
+
if columns is None:
|
|
45
|
+
columns = []
|
|
46
|
+
self.var_type = var_type
|
|
47
|
+
# Normalize the all-columns sentinel to an empty list (copying any
|
|
48
|
+
# provided list so a shared default object is never mutated).
|
|
49
|
+
self.columns = list(columns) if columns else []
|
|
50
|
+
self.iterate = iterate
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def __name__(self) -> str:
|
|
54
|
+
"""Return a display name for format_inputs and error messages."""
|
|
55
|
+
var_name = getattr(self.var_type, "__name__", type(self.var_type).__name__)
|
|
56
|
+
suffix = ", iterate" if self.iterate else ""
|
|
57
|
+
if not self.columns:
|
|
58
|
+
return f"{var_name}[<all columns>{suffix}]"
|
|
59
|
+
if len(self.columns) == 1 and not self.iterate:
|
|
60
|
+
return f'{var_name}["{self.columns[0]}"]'
|
|
61
|
+
cols = ", ".join(f'"{c}"' for c in self.columns)
|
|
62
|
+
return f"{var_name}[{cols}{suffix}]"
|
|
63
|
+
|
|
64
|
+
def load(self, **metadata) -> Any:
|
|
65
|
+
"""Load from the underlying var_type, then apply column selection."""
|
|
66
|
+
return self.var_type.load(**metadata)
|
|
67
|
+
|
|
68
|
+
def to_csv(self, filename: str, *args, **kwargs) -> None:
|
|
69
|
+
"""Export the selected column(s) to a CSV file in flat table format.
|
|
70
|
+
|
|
71
|
+
Writes one row per schema_id with the selected columns as value
|
|
72
|
+
columns. The underlying variable must be a single-row table per
|
|
73
|
+
schema_id. ``filename`` must end with ``.csv``. ``kwargs`` mirror
|
|
74
|
+
``load()`` (``where=``, ``version=``, ``db=``, metadata).
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
GaitData["Speed"].to_csv("speed.csv", subject=1)
|
|
78
|
+
GaitData[["Speed", "Cadence"]].to_csv("gait.csv", subject=1)
|
|
79
|
+
"""
|
|
80
|
+
from scidb.csv_export import export_csv
|
|
81
|
+
|
|
82
|
+
export_csv(self, filename, *args, **kwargs)
|
|
83
|
+
|
|
84
|
+
# --- Comparison operators that produce ColumnFilter objects ---
|
|
85
|
+
|
|
86
|
+
def __eq__(self, other):
|
|
87
|
+
try:
|
|
88
|
+
from scidb.filters import ColumnFilter
|
|
89
|
+
|
|
90
|
+
return ColumnFilter(self.var_type, self.columns[0], "==", other)
|
|
91
|
+
except ImportError:
|
|
92
|
+
raise NotImplementedError(
|
|
93
|
+
"Comparison operators on ColumnSelection require scidb."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def __ne__(self, other):
|
|
97
|
+
try:
|
|
98
|
+
from scidb.filters import ColumnFilter
|
|
99
|
+
|
|
100
|
+
return ColumnFilter(self.var_type, self.columns[0], "!=", other)
|
|
101
|
+
except ImportError:
|
|
102
|
+
raise NotImplementedError(
|
|
103
|
+
"Comparison operators on ColumnSelection require scidb."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def __lt__(self, other):
|
|
107
|
+
try:
|
|
108
|
+
from scidb.filters import ColumnFilter
|
|
109
|
+
|
|
110
|
+
return ColumnFilter(self.var_type, self.columns[0], "<", other)
|
|
111
|
+
except ImportError:
|
|
112
|
+
raise NotImplementedError(
|
|
113
|
+
"Comparison operators on ColumnSelection require scidb."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def __le__(self, other):
|
|
117
|
+
try:
|
|
118
|
+
from scidb.filters import ColumnFilter
|
|
119
|
+
|
|
120
|
+
return ColumnFilter(self.var_type, self.columns[0], "<=", other)
|
|
121
|
+
except ImportError:
|
|
122
|
+
raise NotImplementedError(
|
|
123
|
+
"Comparison operators on ColumnSelection require scidb."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def __gt__(self, other):
|
|
127
|
+
try:
|
|
128
|
+
from scidb.filters import ColumnFilter
|
|
129
|
+
|
|
130
|
+
return ColumnFilter(self.var_type, self.columns[0], ">", other)
|
|
131
|
+
except ImportError:
|
|
132
|
+
raise NotImplementedError(
|
|
133
|
+
"Comparison operators on ColumnSelection require scidb."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def __ge__(self, other):
|
|
137
|
+
try:
|
|
138
|
+
from scidb.filters import ColumnFilter
|
|
139
|
+
|
|
140
|
+
return ColumnFilter(self.var_type, self.columns[0], ">=", other)
|
|
141
|
+
except ImportError:
|
|
142
|
+
raise NotImplementedError(
|
|
143
|
+
"Comparison operators on ColumnSelection require scidb."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def isin(self, values):
|
|
147
|
+
"""Create an InFilter for set membership testing."""
|
|
148
|
+
try:
|
|
149
|
+
from scidb.filters import InFilter
|
|
150
|
+
|
|
151
|
+
return InFilter(self.var_type, self.columns[0], list(values))
|
|
152
|
+
except ImportError:
|
|
153
|
+
raise NotImplementedError("isin() on ColumnSelection requires scidb.")
|
|
154
|
+
|
|
155
|
+
def to_key(self) -> str:
|
|
156
|
+
"""Return a canonical string for use as a version key.
|
|
157
|
+
|
|
158
|
+
Includes ``iterate`` and the resolved column list so that changing the
|
|
159
|
+
iterated column set (including the empty ``[]`` -> all-columns
|
|
160
|
+
resolution done before this is called) invalidates cached results.
|
|
161
|
+
"""
|
|
162
|
+
name = getattr(self.var_type, "__name__", repr(self.var_type))
|
|
163
|
+
return f"{name}[{self.columns!r}, iterate={self.iterate}]"
|
|
164
|
+
|
|
165
|
+
def __hash__(self):
|
|
166
|
+
return hash((self.var_type, tuple(self.columns), self.iterate))
|
scidb/constant.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constant — a lightweight wrapper for pipeline configuration values.
|
|
3
|
+
|
|
4
|
+
Scientific pipelines accumulate magic numbers: sampling rates, bandpass limits,
|
|
5
|
+
epoch durations, regularization weights, etc. Wrapping them in ``constant(...)``
|
|
6
|
+
gives each value a description and a source location so the GUI sidebar can
|
|
7
|
+
surface them, while still letting the value behave transparently like its
|
|
8
|
+
underlying type.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
from scidb import constant
|
|
12
|
+
|
|
13
|
+
SAMPLING_RATE_HZ = constant(
|
|
14
|
+
1000,
|
|
15
|
+
description="Default sampling rate for all recordings",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
DEFAULT_BANDPASS = constant(
|
|
19
|
+
(1.0, 40.0),
|
|
20
|
+
description="Standard LFP bandpass (Hz)",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Transparent use:
|
|
24
|
+
duration = 5 * SAMPLING_RATE_HZ # 5000
|
|
25
|
+
low = DEFAULT_BANDPASS[0] # 1.0
|
|
26
|
+
|
|
27
|
+
# Discovery-side detection:
|
|
28
|
+
isinstance(SAMPLING_RATE_HZ, Constant) # True
|
|
29
|
+
SAMPLING_RATE_HZ.description # "Default sampling rate ..."
|
|
30
|
+
SAMPLING_RATE_HZ.source_file # "/path/to/module.py"
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import inspect
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Constant:
|
|
40
|
+
"""
|
|
41
|
+
Transparent wrapper around a pipeline configuration value.
|
|
42
|
+
|
|
43
|
+
``Constant`` instances forward arithmetic, comparison, container, and
|
|
44
|
+
attribute access to the underlying value so they can be used in place of
|
|
45
|
+
the raw value at call sites. The wrapper additionally carries a human
|
|
46
|
+
description and the source file/line where it was constructed, which the
|
|
47
|
+
scidb discovery scanner uses to populate the GUI sidebar.
|
|
48
|
+
|
|
49
|
+
Prefer the :func:`constant` factory over instantiating this class directly
|
|
50
|
+
— the factory captures the caller's source location automatically.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
__slots__ = ("_value", "description", "source_file", "source_line")
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
value: Any,
|
|
58
|
+
description: str = "",
|
|
59
|
+
source_file: str = "",
|
|
60
|
+
source_line: int = 0,
|
|
61
|
+
) -> None:
|
|
62
|
+
# Bypass __setattr__ on __slots__ via object.__setattr__ to make the
|
|
63
|
+
# wrapper feel as immutable as possible from the outside.
|
|
64
|
+
object.__setattr__(self, "_value", value)
|
|
65
|
+
object.__setattr__(self, "description", description)
|
|
66
|
+
object.__setattr__(self, "source_file", source_file)
|
|
67
|
+
object.__setattr__(self, "source_line", source_line)
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# Introspection / debugging
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
@property
|
|
73
|
+
def value(self) -> Any:
|
|
74
|
+
"""Return the wrapped underlying value."""
|
|
75
|
+
return self._value
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return f"Constant({self._value!r}, description={self.description!r})"
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
# Attribute passthrough
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
def __getattr__(self, name: str) -> Any:
|
|
84
|
+
# __getattr__ is only called when normal lookup fails, so our own
|
|
85
|
+
# slots (description, source_file, ...) take precedence.
|
|
86
|
+
return getattr(self._value, name)
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# Conversions
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
def __bool__(self) -> bool:
|
|
92
|
+
return bool(self._value)
|
|
93
|
+
|
|
94
|
+
def __int__(self) -> int:
|
|
95
|
+
return int(self._value)
|
|
96
|
+
|
|
97
|
+
def __float__(self) -> float:
|
|
98
|
+
return float(self._value)
|
|
99
|
+
|
|
100
|
+
def __complex__(self) -> complex:
|
|
101
|
+
return complex(self._value)
|
|
102
|
+
|
|
103
|
+
def __str__(self) -> str:
|
|
104
|
+
return str(self._value)
|
|
105
|
+
|
|
106
|
+
def __format__(self, format_spec: str) -> str:
|
|
107
|
+
return format(self._value, format_spec)
|
|
108
|
+
|
|
109
|
+
def __bytes__(self) -> bytes:
|
|
110
|
+
return bytes(self._value)
|
|
111
|
+
|
|
112
|
+
def __index__(self) -> int:
|
|
113
|
+
# Needed for things like ``range(SAMPLING_RATE_HZ)`` and slicing.
|
|
114
|
+
return self._value.__index__()
|
|
115
|
+
|
|
116
|
+
def __hash__(self) -> int:
|
|
117
|
+
return hash(self._value)
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Comparison
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _unwrap(other: Any) -> Any:
|
|
124
|
+
return other._value if isinstance(other, Constant) else other
|
|
125
|
+
|
|
126
|
+
def __eq__(self, other: Any) -> bool:
|
|
127
|
+
return self._value == self._unwrap(other)
|
|
128
|
+
|
|
129
|
+
def __ne__(self, other: Any) -> bool:
|
|
130
|
+
return self._value != self._unwrap(other)
|
|
131
|
+
|
|
132
|
+
def __lt__(self, other: Any) -> bool:
|
|
133
|
+
return self._value < self._unwrap(other)
|
|
134
|
+
|
|
135
|
+
def __le__(self, other: Any) -> bool:
|
|
136
|
+
return self._value <= self._unwrap(other)
|
|
137
|
+
|
|
138
|
+
def __gt__(self, other: Any) -> bool:
|
|
139
|
+
return self._value > self._unwrap(other)
|
|
140
|
+
|
|
141
|
+
def __ge__(self, other: Any) -> bool:
|
|
142
|
+
return self._value >= self._unwrap(other)
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------
|
|
145
|
+
# Arithmetic (left-hand)
|
|
146
|
+
# ------------------------------------------------------------------
|
|
147
|
+
def __add__(self, other: Any) -> Any:
|
|
148
|
+
return self._value + self._unwrap(other)
|
|
149
|
+
|
|
150
|
+
def __sub__(self, other: Any) -> Any:
|
|
151
|
+
return self._value - self._unwrap(other)
|
|
152
|
+
|
|
153
|
+
def __mul__(self, other: Any) -> Any:
|
|
154
|
+
return self._value * self._unwrap(other)
|
|
155
|
+
|
|
156
|
+
def __truediv__(self, other: Any) -> Any:
|
|
157
|
+
return self._value / self._unwrap(other)
|
|
158
|
+
|
|
159
|
+
def __floordiv__(self, other: Any) -> Any:
|
|
160
|
+
return self._value // self._unwrap(other)
|
|
161
|
+
|
|
162
|
+
def __mod__(self, other: Any) -> Any:
|
|
163
|
+
return self._value % self._unwrap(other)
|
|
164
|
+
|
|
165
|
+
def __pow__(self, other: Any, mod: Any = None) -> Any:
|
|
166
|
+
if mod is None:
|
|
167
|
+
return self._value ** self._unwrap(other)
|
|
168
|
+
return pow(self._value, self._unwrap(other), mod)
|
|
169
|
+
|
|
170
|
+
def __matmul__(self, other: Any) -> Any:
|
|
171
|
+
return self._value @ self._unwrap(other)
|
|
172
|
+
|
|
173
|
+
def __lshift__(self, other: Any) -> Any:
|
|
174
|
+
return self._value << self._unwrap(other)
|
|
175
|
+
|
|
176
|
+
def __rshift__(self, other: Any) -> Any:
|
|
177
|
+
return self._value >> self._unwrap(other)
|
|
178
|
+
|
|
179
|
+
def __and__(self, other: Any) -> Any:
|
|
180
|
+
return self._value & self._unwrap(other)
|
|
181
|
+
|
|
182
|
+
def __xor__(self, other: Any) -> Any:
|
|
183
|
+
return self._value ^ self._unwrap(other)
|
|
184
|
+
|
|
185
|
+
def __or__(self, other: Any) -> Any:
|
|
186
|
+
return self._value | self._unwrap(other)
|
|
187
|
+
|
|
188
|
+
# ------------------------------------------------------------------
|
|
189
|
+
# Arithmetic (right-hand)
|
|
190
|
+
# ------------------------------------------------------------------
|
|
191
|
+
def __radd__(self, other: Any) -> Any:
|
|
192
|
+
return self._unwrap(other) + self._value
|
|
193
|
+
|
|
194
|
+
def __rsub__(self, other: Any) -> Any:
|
|
195
|
+
return self._unwrap(other) - self._value
|
|
196
|
+
|
|
197
|
+
def __rmul__(self, other: Any) -> Any:
|
|
198
|
+
return self._unwrap(other) * self._value
|
|
199
|
+
|
|
200
|
+
def __rtruediv__(self, other: Any) -> Any:
|
|
201
|
+
return self._unwrap(other) / self._value
|
|
202
|
+
|
|
203
|
+
def __rfloordiv__(self, other: Any) -> Any:
|
|
204
|
+
return self._unwrap(other) // self._value
|
|
205
|
+
|
|
206
|
+
def __rmod__(self, other: Any) -> Any:
|
|
207
|
+
return self._unwrap(other) % self._value
|
|
208
|
+
|
|
209
|
+
def __rpow__(self, other: Any) -> Any:
|
|
210
|
+
return self._unwrap(other) ** self._value
|
|
211
|
+
|
|
212
|
+
def __rmatmul__(self, other: Any) -> Any:
|
|
213
|
+
return self._unwrap(other) @ self._value
|
|
214
|
+
|
|
215
|
+
def __rlshift__(self, other: Any) -> Any:
|
|
216
|
+
return self._unwrap(other) << self._value
|
|
217
|
+
|
|
218
|
+
def __rrshift__(self, other: Any) -> Any:
|
|
219
|
+
return self._unwrap(other) >> self._value
|
|
220
|
+
|
|
221
|
+
def __rand__(self, other: Any) -> Any:
|
|
222
|
+
return self._unwrap(other) & self._value
|
|
223
|
+
|
|
224
|
+
def __rxor__(self, other: Any) -> Any:
|
|
225
|
+
return self._unwrap(other) ^ self._value
|
|
226
|
+
|
|
227
|
+
def __ror__(self, other: Any) -> Any:
|
|
228
|
+
return self._unwrap(other) | self._value
|
|
229
|
+
|
|
230
|
+
# ------------------------------------------------------------------
|
|
231
|
+
# Unary
|
|
232
|
+
# ------------------------------------------------------------------
|
|
233
|
+
def __neg__(self) -> Any:
|
|
234
|
+
return -self._value
|
|
235
|
+
|
|
236
|
+
def __pos__(self) -> Any:
|
|
237
|
+
return +self._value
|
|
238
|
+
|
|
239
|
+
def __abs__(self) -> Any:
|
|
240
|
+
return abs(self._value)
|
|
241
|
+
|
|
242
|
+
def __invert__(self) -> Any:
|
|
243
|
+
return ~self._value
|
|
244
|
+
|
|
245
|
+
def __round__(self, ndigits: int | None = None) -> Any:
|
|
246
|
+
if ndigits is None:
|
|
247
|
+
return round(self._value)
|
|
248
|
+
return round(self._value, ndigits)
|
|
249
|
+
|
|
250
|
+
# ------------------------------------------------------------------
|
|
251
|
+
# Container / iteration
|
|
252
|
+
# ------------------------------------------------------------------
|
|
253
|
+
def __len__(self) -> int:
|
|
254
|
+
return len(self._value)
|
|
255
|
+
|
|
256
|
+
def __iter__(self):
|
|
257
|
+
return iter(self._value)
|
|
258
|
+
|
|
259
|
+
def __contains__(self, item: Any) -> bool:
|
|
260
|
+
return self._unwrap(item) in self._value
|
|
261
|
+
|
|
262
|
+
def __getitem__(self, key: Any) -> Any:
|
|
263
|
+
return self._value[self._unwrap(key)]
|
|
264
|
+
|
|
265
|
+
def __reversed__(self):
|
|
266
|
+
return reversed(self._value)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def constant(value: Any, description: str = "") -> Constant:
|
|
270
|
+
"""
|
|
271
|
+
Wrap ``value`` in a :class:`Constant`, capturing the caller's source
|
|
272
|
+
location so the GUI sidebar can link back to the definition.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
value: The underlying value (scalar, tuple, list, dict, etc.).
|
|
276
|
+
description: Human-readable description of what the constant is for.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
A :class:`Constant` instance that behaves transparently as ``value``
|
|
280
|
+
for arithmetic, comparison, container, and attribute access.
|
|
281
|
+
"""
|
|
282
|
+
# inspect.stack()[0] is this frame; [1] is the caller.
|
|
283
|
+
caller = inspect.stack()[1]
|
|
284
|
+
try:
|
|
285
|
+
source_file = caller.filename
|
|
286
|
+
source_line = caller.lineno
|
|
287
|
+
finally:
|
|
288
|
+
# Break the frame reference cycle that ``inspect.stack`` creates.
|
|
289
|
+
del caller
|
|
290
|
+
return Constant(
|
|
291
|
+
value,
|
|
292
|
+
description=description,
|
|
293
|
+
source_file=source_file,
|
|
294
|
+
source_line=source_line,
|
|
295
|
+
)
|