pyformula 0.1.0__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.
- pyformula/__init__.py +26 -0
- pyformula/compiler/__init__.py +0 -0
- pyformula/compiler/compiler.py +160 -0
- pyformula/compiler/models.py +330 -0
- pyformula/exceptions.py +29 -0
- pyformula/formula.py +181 -0
- pyformula/math.py +562 -0
- pyformula/models.py +13 -0
- pyformula/operator.py +52 -0
- pyformula/registry.py +123 -0
- pyformula/variable.py +60 -0
- pyformula-0.1.0.dist-info/METADATA +941 -0
- pyformula-0.1.0.dist-info/RECORD +15 -0
- pyformula-0.1.0.dist-info/WHEEL +4 -0
- pyformula-0.1.0.dist-info/entry_points.txt +3 -0
pyformula/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from .compiler.compiler import FormulaCompiler
|
|
2
|
+
from .compiler.models import Expression
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
FormulaAlreadyRegisteredError,
|
|
5
|
+
FormulaNotFoundError,
|
|
6
|
+
FormulaNotRegisteredError,
|
|
7
|
+
)
|
|
8
|
+
from .formula import Formula
|
|
9
|
+
from .models import Number, Operator
|
|
10
|
+
from .operator import OperatorFn
|
|
11
|
+
from .registry import VariablesRegistry
|
|
12
|
+
from .variable import variable
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Expression",
|
|
16
|
+
"Formula",
|
|
17
|
+
"FormulaAlreadyRegisteredError",
|
|
18
|
+
"FormulaCompiler",
|
|
19
|
+
"FormulaNotFoundError",
|
|
20
|
+
"FormulaNotRegisteredError",
|
|
21
|
+
"Number",
|
|
22
|
+
"Operator",
|
|
23
|
+
"OperatorFn",
|
|
24
|
+
"VariablesRegistry",
|
|
25
|
+
"variable",
|
|
26
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
from pyformula import math
|
|
6
|
+
from pyformula.exceptions import FormulaNotFoundError, InvalidExpressionError
|
|
7
|
+
from pyformula.formula import Formula
|
|
8
|
+
from pyformula.operator import OPERATORS
|
|
9
|
+
|
|
10
|
+
from .models import Expression, FormulaDict, is_formula_dict, is_round_wrapper_dict
|
|
11
|
+
|
|
12
|
+
WRAPPER_FNS: dict[str, Callable[[Formula[Any]], Formula[Any]]] = {
|
|
13
|
+
"positive": lambda fm: +fm,
|
|
14
|
+
"negative": lambda fm: -fm,
|
|
15
|
+
"absolute": abs,
|
|
16
|
+
"ceil": math.ceil,
|
|
17
|
+
"floor": math.floor,
|
|
18
|
+
"trunc": math.trunc,
|
|
19
|
+
"sqrt": math.sqrt,
|
|
20
|
+
"cbrt": math.cbrt,
|
|
21
|
+
"exp": math.exp,
|
|
22
|
+
"exp2": math.exp2,
|
|
23
|
+
"expm1": math.expm1,
|
|
24
|
+
"log10": math.log10,
|
|
25
|
+
"log1p": math.log1p,
|
|
26
|
+
"log2": math.log2,
|
|
27
|
+
"sin": math.sin,
|
|
28
|
+
"sinh": math.sinh,
|
|
29
|
+
"asin": math.asin,
|
|
30
|
+
"asinh": math.asinh,
|
|
31
|
+
"cos": math.cos,
|
|
32
|
+
"cosh": math.cosh,
|
|
33
|
+
"acos": math.acos,
|
|
34
|
+
"acosh": math.acosh,
|
|
35
|
+
"tan": math.tan,
|
|
36
|
+
"tanh": math.tanh,
|
|
37
|
+
"atan": math.atan,
|
|
38
|
+
"atanh": math.atanh,
|
|
39
|
+
"degrees": math.degrees,
|
|
40
|
+
"radians": math.radians,
|
|
41
|
+
"erf": math.erf,
|
|
42
|
+
"erfc": math.erfc,
|
|
43
|
+
"gamma": math.gamma,
|
|
44
|
+
"lgamma": math.lgamma,
|
|
45
|
+
"fabs": math.fabs,
|
|
46
|
+
"ulp": math.ulp,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class FormulaCompiler[T]:
|
|
51
|
+
"""Compile a structured expression tree into a runtime formula.
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
```python
|
|
55
|
+
from dataclasses import dataclass
|
|
56
|
+
from pyformula import Formula
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Rectangular:
|
|
61
|
+
width: float
|
|
62
|
+
height: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> None:
|
|
66
|
+
fms: dict[str, Formula[Rectangular]] = {
|
|
67
|
+
"width": Formula[Rectangular](lambda rectangle: rectangle.width),
|
|
68
|
+
"height": Formula[Rectangular](lambda rectangle: rectangle.height),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
compiler = FormulaCompiler[Rectangular](fms)
|
|
72
|
+
|
|
73
|
+
perimeter = compiler.compile(
|
|
74
|
+
{
|
|
75
|
+
"operator": "multiply",
|
|
76
|
+
"expressions":[
|
|
77
|
+
"operator": "add",
|
|
78
|
+
"expressions": [
|
|
79
|
+
"width",
|
|
80
|
+
"height",
|
|
81
|
+
], 2,
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
area = compiler.compile(
|
|
87
|
+
{
|
|
88
|
+
"operator": "multiply",
|
|
89
|
+
"expressions": [
|
|
90
|
+
"width",
|
|
91
|
+
"height",
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
print(perimeter(Rectangular(width=10, height=20))) # 60.0
|
|
97
|
+
print(area(Rectangular(width=10, height=20))) # 200.0
|
|
98
|
+
|
|
99
|
+
print(perimeter(Rectangular(width=5, height=10))) # 30.0
|
|
100
|
+
print(area(Rectangular(width=5, height=10))) # 50.0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, fns: dict[str, Formula[T]], /) -> None:
|
|
111
|
+
self._fns = fns
|
|
112
|
+
|
|
113
|
+
def compile(self, expression: Expression) -> Formula[T]:
|
|
114
|
+
"""Compile an expression object into a formula callable for a given model type."""
|
|
115
|
+
if isinstance(expression, (int, float, Decimal)):
|
|
116
|
+
return Formula(lambda _: expression)
|
|
117
|
+
|
|
118
|
+
if isinstance(expression, str) and expression not in self._fns:
|
|
119
|
+
raise FormulaNotFoundError(expression)
|
|
120
|
+
|
|
121
|
+
if isinstance(expression, str):
|
|
122
|
+
return self._fns[expression]
|
|
123
|
+
|
|
124
|
+
if is_formula_dict(expression):
|
|
125
|
+
return self._compile_formula(expression)
|
|
126
|
+
|
|
127
|
+
if is_round_wrapper_dict(expression) and not isinstance(expression["ndigits"], int):
|
|
128
|
+
raise InvalidExpressionError(expression)
|
|
129
|
+
|
|
130
|
+
if is_round_wrapper_dict(expression):
|
|
131
|
+
return round(self.compile(expression["round"]), ndigits=expression["ndigits"])
|
|
132
|
+
|
|
133
|
+
if not isinstance(expression, dict):
|
|
134
|
+
raise InvalidExpressionError(expression)
|
|
135
|
+
|
|
136
|
+
for fn_name, wrap in WRAPPER_FNS.items():
|
|
137
|
+
if fn_name in expression and len(expression) == 1:
|
|
138
|
+
return wrap(self.compile(expression[fn_name]))
|
|
139
|
+
|
|
140
|
+
raise InvalidExpressionError(expression)
|
|
141
|
+
|
|
142
|
+
def _compile_formula(self, fm_dict: FormulaDict) -> Formula[T]:
|
|
143
|
+
expressions = fm_dict["expressions"]
|
|
144
|
+
operator = fm_dict["operator"]
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
not isinstance(expressions, list)
|
|
148
|
+
or not expressions
|
|
149
|
+
or not isinstance(operator, str)
|
|
150
|
+
or operator not in OPERATORS
|
|
151
|
+
):
|
|
152
|
+
raise InvalidExpressionError(fm_dict)
|
|
153
|
+
|
|
154
|
+
formula = self.compile(expressions[0])
|
|
155
|
+
applier, _ = OPERATORS[operator]
|
|
156
|
+
|
|
157
|
+
for expression in expressions[1:]:
|
|
158
|
+
formula = applier(formula, self.compile(expression))
|
|
159
|
+
|
|
160
|
+
return cast("Formula[T]", formula)
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
from typing import TypeAlias, TypedDict, TypeGuard
|
|
2
|
+
|
|
3
|
+
from pyformula.models import Number, Operator
|
|
4
|
+
|
|
5
|
+
# -----------------------
|
|
6
|
+
# basic operators
|
|
7
|
+
# -----------------------
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PositiveWrapperDict(TypedDict):
|
|
11
|
+
"""Typed dict for a unary positive wrapper expression."""
|
|
12
|
+
|
|
13
|
+
positive: "Expression"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NegativeWrapperDict(TypedDict):
|
|
17
|
+
"""Typed dict for a unary negative wrapper expression."""
|
|
18
|
+
|
|
19
|
+
negative: "Expression"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AbsoluteWrapperDict(TypedDict):
|
|
23
|
+
"""Typed dict for an absolute-value wrapper expression."""
|
|
24
|
+
|
|
25
|
+
absolute: "Expression"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RoundWrapperDict(TypedDict):
|
|
29
|
+
"""Typed dict for a round wrapper that carries an integer precision."""
|
|
30
|
+
|
|
31
|
+
round: "Expression"
|
|
32
|
+
ndigits: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_round_wrapper_dict(obj: object) -> TypeGuard[RoundWrapperDict]:
|
|
36
|
+
"""Return whether the object matches the round-wrapper schema."""
|
|
37
|
+
return _is_typed_dict(obj, "round", "ndigits")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# -----------------------
|
|
41
|
+
# rounding
|
|
42
|
+
# -----------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FloorWrapperDict(TypedDict):
|
|
46
|
+
"""Typed dict for a floor wrapper expression."""
|
|
47
|
+
|
|
48
|
+
floor: "Expression"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CeilWrapperDict(TypedDict):
|
|
52
|
+
"""Typed dict for a ceil wrapper expression."""
|
|
53
|
+
|
|
54
|
+
ceil: "Expression"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TruncWrapperDict(TypedDict):
|
|
58
|
+
"""Typed dict for a trunc wrapper expression."""
|
|
59
|
+
|
|
60
|
+
trunc: "Expression"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# -----------------------
|
|
64
|
+
# roots and powers
|
|
65
|
+
# -----------------------
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SqrtWrapperDict(TypedDict):
|
|
69
|
+
"""Typed dict for a square-root wrapper expression."""
|
|
70
|
+
|
|
71
|
+
sqrt: "Expression"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CbrtWrapperDict(TypedDict):
|
|
75
|
+
"""Typed dict for a cube-root wrapper expression."""
|
|
76
|
+
|
|
77
|
+
cbrt: "Expression"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ----------------------------------------------
|
|
81
|
+
# exponential and logarithmic
|
|
82
|
+
# ----------------------------------------------
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ExpWrapperDict(TypedDict):
|
|
86
|
+
"""Typed dict for an exp wrapper expression."""
|
|
87
|
+
|
|
88
|
+
exp: "Expression"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Exp2WrapperDict(TypedDict):
|
|
92
|
+
"""Typed dict for an exp2 wrapper expression."""
|
|
93
|
+
|
|
94
|
+
exp2: "Expression"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Expm1WrapperDict(TypedDict):
|
|
98
|
+
"""Typed dict for an expm1 wrapper expression."""
|
|
99
|
+
|
|
100
|
+
expm1: "Expression"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Log10WrapperDict(TypedDict):
|
|
104
|
+
"""Typed dict for a log10 wrapper expression."""
|
|
105
|
+
|
|
106
|
+
log10: "Expression"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Log1pWrapperDict(TypedDict):
|
|
110
|
+
"""Typed dict for a log1p wrapper expression."""
|
|
111
|
+
|
|
112
|
+
log1p: "Expression"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Log2WrapperDict(TypedDict):
|
|
116
|
+
"""Typed dict for a log2 wrapper expression."""
|
|
117
|
+
|
|
118
|
+
log2: "Expression"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ----------------------------------------------
|
|
122
|
+
# trigonometry
|
|
123
|
+
# ----------------------------------------------
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SinWrapperDict(TypedDict):
|
|
127
|
+
"""Typed dict for a sine wrapper expression."""
|
|
128
|
+
|
|
129
|
+
sin: "Expression"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class SinhWrapperDict(TypedDict):
|
|
133
|
+
"""Typed dict for a hyperbolic sine wrapper expression."""
|
|
134
|
+
|
|
135
|
+
sinh: "Expression"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class AsinWrapperDict(TypedDict):
|
|
139
|
+
"""Typed dict for an arc-sine wrapper expression."""
|
|
140
|
+
|
|
141
|
+
asin: "Expression"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AsinhWrapperDict(TypedDict):
|
|
145
|
+
"""Typed dict for an inverse hyperbolic sine wrapper expression."""
|
|
146
|
+
|
|
147
|
+
asinh: "Expression"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class CosWrapperDict(TypedDict):
|
|
151
|
+
"""Typed dict for a cosine wrapper expression."""
|
|
152
|
+
|
|
153
|
+
cos: "Expression"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class CoshWrapperDict(TypedDict):
|
|
157
|
+
"""Typed dict for a hyperbolic cosine wrapper expression."""
|
|
158
|
+
|
|
159
|
+
cosh: "Expression"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class AcosWrapperDict(TypedDict):
|
|
163
|
+
"""Typed dict for an arc-cosine wrapper expression."""
|
|
164
|
+
|
|
165
|
+
acos: "Expression"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class AcoshWrapperDict(TypedDict):
|
|
169
|
+
"""Typed dict for an inverse hyperbolic cosine wrapper expression."""
|
|
170
|
+
|
|
171
|
+
acosh: "Expression"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TanWrapperDict(TypedDict):
|
|
175
|
+
"""Typed dict for a tangent wrapper expression."""
|
|
176
|
+
|
|
177
|
+
tan: "Expression"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TanhWrapperDict(TypedDict):
|
|
181
|
+
"""Typed dict for a hyperbolic tangent wrapper expression."""
|
|
182
|
+
|
|
183
|
+
tanh: "Expression"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class AtanWrapperDict(TypedDict):
|
|
187
|
+
"""Typed dict for an arc-tangent wrapper expression."""
|
|
188
|
+
|
|
189
|
+
atan: "Expression"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class AtanhWrapperDict(TypedDict):
|
|
193
|
+
"""Typed dict for an inverse hyperbolic tangent wrapper expression."""
|
|
194
|
+
|
|
195
|
+
atanh: "Expression"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ----------------------------------------------
|
|
199
|
+
# angular conversion
|
|
200
|
+
# ----------------------------------------------
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class DegreesWrapperDict(TypedDict):
|
|
204
|
+
"""Typed dict for a degrees-conversion wrapper expression."""
|
|
205
|
+
|
|
206
|
+
degrees: "Expression"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class RadiansWrapperDict(TypedDict):
|
|
210
|
+
"""Typed dict for a radians-conversion wrapper expression."""
|
|
211
|
+
|
|
212
|
+
radians: "Expression"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ----------------------------------------------
|
|
216
|
+
# special functions
|
|
217
|
+
# ----------------------------------------------
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ErfWrapperDict(TypedDict):
|
|
221
|
+
"""Typed dict for an error-function wrapper expression."""
|
|
222
|
+
|
|
223
|
+
erf: "Expression"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ErfcWrapperDict(TypedDict):
|
|
227
|
+
"""Typed dict for a complementary error-function wrapper expression."""
|
|
228
|
+
|
|
229
|
+
erfc: "Expression"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class GammaWrapperDict(TypedDict):
|
|
233
|
+
"""Typed dict for a gamma-function wrapper expression."""
|
|
234
|
+
|
|
235
|
+
gamma: "Expression"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class LgammaWrapperDict(TypedDict):
|
|
239
|
+
"""Typed dict for a logarithmic gamma wrapper expression."""
|
|
240
|
+
|
|
241
|
+
lgamma: "Expression"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class FabsWrapperDict(TypedDict):
|
|
245
|
+
"""Typed dict for a floating-point absolute-value wrapper expression."""
|
|
246
|
+
|
|
247
|
+
fabs: "Expression"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class UlpWrapperDict(TypedDict):
|
|
251
|
+
"""Typed dict for an ulp wrapper expression."""
|
|
252
|
+
|
|
253
|
+
ulp: "Expression"
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# -----------------------
|
|
257
|
+
# formula
|
|
258
|
+
# -----------------------
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
Expression: TypeAlias = "FormulaDict | WrapperDict | str | Number"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
WrapperDict: TypeAlias = (
|
|
265
|
+
# basic operators
|
|
266
|
+
PositiveWrapperDict
|
|
267
|
+
| NegativeWrapperDict
|
|
268
|
+
| AbsoluteWrapperDict
|
|
269
|
+
| RoundWrapperDict
|
|
270
|
+
# rounding
|
|
271
|
+
| FloorWrapperDict
|
|
272
|
+
| CeilWrapperDict
|
|
273
|
+
| TruncWrapperDict
|
|
274
|
+
# roots and powers
|
|
275
|
+
| SqrtWrapperDict
|
|
276
|
+
| CbrtWrapperDict
|
|
277
|
+
# exponential and logarithmic
|
|
278
|
+
| ExpWrapperDict
|
|
279
|
+
| Exp2WrapperDict
|
|
280
|
+
| Expm1WrapperDict
|
|
281
|
+
| Log10WrapperDict
|
|
282
|
+
| Log1pWrapperDict
|
|
283
|
+
| Log2WrapperDict
|
|
284
|
+
# trigonometry
|
|
285
|
+
| SinWrapperDict
|
|
286
|
+
| SinhWrapperDict
|
|
287
|
+
| AsinWrapperDict
|
|
288
|
+
| AsinhWrapperDict
|
|
289
|
+
| CosWrapperDict
|
|
290
|
+
| CoshWrapperDict
|
|
291
|
+
| AcosWrapperDict
|
|
292
|
+
| AcoshWrapperDict
|
|
293
|
+
| TanWrapperDict
|
|
294
|
+
| TanhWrapperDict
|
|
295
|
+
| AtanWrapperDict
|
|
296
|
+
| AtanhWrapperDict
|
|
297
|
+
# angular conversion
|
|
298
|
+
| DegreesWrapperDict
|
|
299
|
+
| RadiansWrapperDict
|
|
300
|
+
# special functions
|
|
301
|
+
| ErfWrapperDict
|
|
302
|
+
| ErfcWrapperDict
|
|
303
|
+
| GammaWrapperDict
|
|
304
|
+
| LgammaWrapperDict
|
|
305
|
+
| FabsWrapperDict
|
|
306
|
+
| UlpWrapperDict
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class FormulaDict(TypedDict):
|
|
311
|
+
"""Typed dict describing a binary operation expression tree."""
|
|
312
|
+
|
|
313
|
+
operator: Operator
|
|
314
|
+
expressions: list["Expression"]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def is_formula_dict(obj: object) -> TypeGuard[FormulaDict]:
|
|
318
|
+
"""Return whether the object matches the binary-formula schema."""
|
|
319
|
+
return _is_typed_dict(obj, "operator", "expressions")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# -----------------------
|
|
323
|
+
# helper
|
|
324
|
+
# -----------------------
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _is_typed_dict(obj: object, *keys: str) -> bool:
|
|
328
|
+
return (
|
|
329
|
+
isinstance(obj, dict) and all(key in obj for key in keys) and len(obj.keys()) == len(keys)
|
|
330
|
+
)
|
pyformula/exceptions.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FormulaAlreadyRegisteredError(Exception):
|
|
5
|
+
"""Raised when attempting to register a formula name that already exists."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, name: str) -> None:
|
|
8
|
+
super().__init__(f"Formula already registered: {name}")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FormulaNotRegisteredError(Exception):
|
|
12
|
+
"""Raised when a requested formula name has not been registered."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, name: str) -> None:
|
|
15
|
+
super().__init__(f"Formula not registered: {name}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FormulaNotFoundError(Exception):
|
|
19
|
+
"""Raised when a compiled expression references a formula that is not in scope."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, name: str) -> None:
|
|
22
|
+
super().__init__(f"Formula not found: {name}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InvalidExpressionError(Exception):
|
|
26
|
+
"""Raised when an expression structure does not match the compiler schema."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, expression: Any, /) -> None:
|
|
29
|
+
super().__init__(f"Invalid expression: {expression}")
|