datedict 0.1.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.
Potentially problematic release.
This version of datedict might be problematic. Click here for more details.
- datedict-0.1.1/.gitignore +45 -0
- datedict-0.1.1/.gitlab-ci.yml +51 -0
- datedict-0.1.1/LICENSE +21 -0
- datedict-0.1.1/PKG-INFO +50 -0
- datedict-0.1.1/README.md +14 -0
- datedict-0.1.1/pyproject.toml +29 -0
- datedict-0.1.1/src/datedict/DateDict.py +137 -0
- datedict-0.1.1/src/datedict/YearDict.py +103 -0
- datedict-0.1.1/src/datedict/__init__.py +5 -0
- datedict-0.1.1/src/datedict/common.py +14 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
|
|
6
|
+
# Environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
|
|
12
|
+
# Build / packaging
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
.eggs/
|
|
17
|
+
pip-wheel-metadata/
|
|
18
|
+
.wheelhouse/
|
|
19
|
+
|
|
20
|
+
# Test / coverage
|
|
21
|
+
.coverage
|
|
22
|
+
.coverage.*
|
|
23
|
+
.cache/
|
|
24
|
+
pytest.cache/
|
|
25
|
+
htmlcov/
|
|
26
|
+
.tox/
|
|
27
|
+
.nox/
|
|
28
|
+
|
|
29
|
+
# IDE / tooling
|
|
30
|
+
.vscode/
|
|
31
|
+
.idea/
|
|
32
|
+
*.iml
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
35
|
+
|
|
36
|
+
# Logs
|
|
37
|
+
*.log
|
|
38
|
+
|
|
39
|
+
# OS
|
|
40
|
+
.DS_Store
|
|
41
|
+
Thumbs.db
|
|
42
|
+
|
|
43
|
+
# Project
|
|
44
|
+
.github/
|
|
45
|
+
*.local
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
stages: [build, test-release, release]
|
|
2
|
+
|
|
3
|
+
variables:
|
|
4
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
|
|
5
|
+
|
|
6
|
+
build:
|
|
7
|
+
stage: build
|
|
8
|
+
image: python:3.11-bookworm
|
|
9
|
+
script:
|
|
10
|
+
- python -m pip install -U pip build
|
|
11
|
+
- python -m build
|
|
12
|
+
artifacts:
|
|
13
|
+
paths: [dist/]
|
|
14
|
+
expire_in: 1 hour
|
|
15
|
+
|
|
16
|
+
# Publish to TestPyPI on any tag vX.Y.Z
|
|
17
|
+
publish_testpypi:
|
|
18
|
+
stage: test-release
|
|
19
|
+
image: python:3.11-bookworm
|
|
20
|
+
needs:
|
|
21
|
+
- job: build
|
|
22
|
+
artifacts: true
|
|
23
|
+
rules:
|
|
24
|
+
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
|
|
25
|
+
id_tokens:
|
|
26
|
+
TESTPYPI_ID_TOKEN:
|
|
27
|
+
aud: testpypi
|
|
28
|
+
script:
|
|
29
|
+
- python -m pip install -U twine
|
|
30
|
+
- twine upload --repository testpypi dist/*
|
|
31
|
+
environment:
|
|
32
|
+
name: test-release
|
|
33
|
+
|
|
34
|
+
# Manual promotion to PyPI
|
|
35
|
+
publish_pypi:
|
|
36
|
+
stage: release
|
|
37
|
+
image: python:3.11-bookworm
|
|
38
|
+
needs:
|
|
39
|
+
- job: build
|
|
40
|
+
artifacts: true
|
|
41
|
+
- job: publish_testpypi
|
|
42
|
+
rules:
|
|
43
|
+
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
|
|
44
|
+
id_tokens:
|
|
45
|
+
PYPI_ID_TOKEN:
|
|
46
|
+
aud: pypi
|
|
47
|
+
script:
|
|
48
|
+
- python -m pip install -U twine
|
|
49
|
+
- twine upload dist/*
|
|
50
|
+
environment:
|
|
51
|
+
name: release
|
datedict-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Lorenzo Guideri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
datedict-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datedict
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: DateDict and YearDict: date-aware dictionary structures
|
|
5
|
+
Project-URL: Homepage, https://github.com/you/datedict
|
|
6
|
+
Project-URL: Issues, https://github.com/you/datedict/issues
|
|
7
|
+
Author-email: Lorenzo Guideri <lorenzo.guideri@dec-energy.ch>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Lorenzo Guideri
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Typing :: Typed
|
|
34
|
+
Requires-Python: >=3.10
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# datedict
|
|
38
|
+
|
|
39
|
+
`datedict` bundles two data structures: `DateDict` and `YearDict`.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
```bash
|
|
43
|
+
pip install datedict
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from datedict import DateDict, YearDict
|
|
50
|
+
```
|
datedict-0.1.1/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "datedict"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "DateDict and YearDict: date-aware dictionary structures"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Lorenzo Guideri", email = "lorenzo.guideri@dec-energy.ch" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Typing :: Typed"
|
|
18
|
+
]
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/you/datedict"
|
|
23
|
+
Issues = "https://github.com/you/datedict/issues"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/datedict"]
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build]
|
|
29
|
+
exclude = ["tests", ".github", ".gitignore"]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
from .common import _to_decimal, _Z
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DateDict:
|
|
10
|
+
def __init__(self, data=dict()):
|
|
11
|
+
self.data: dict[str, Decimal | None] = data
|
|
12
|
+
|
|
13
|
+
def create(self, start_date, end_date, value: Decimal) -> "DateDict":
|
|
14
|
+
"""
|
|
15
|
+
Create a new graph with a specified range and value.
|
|
16
|
+
The range is defined by start_date and end_date.
|
|
17
|
+
If the value is None, it will not be included in the graph.
|
|
18
|
+
"""
|
|
19
|
+
start = (
|
|
20
|
+
date.fromisoformat(start_date)
|
|
21
|
+
if isinstance(start_date, str)
|
|
22
|
+
else start_date
|
|
23
|
+
)
|
|
24
|
+
end = date.fromisoformat(end_date) if isinstance(end_date, str) else end_date
|
|
25
|
+
self.data = {
|
|
26
|
+
date.strftime("%Y-%m-%d"): (value if value is not None else None)
|
|
27
|
+
for date in (
|
|
28
|
+
start + timedelta(days=i) for i in range((end - start).days + 1)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def get(self, key: str, default: Decimal) -> Decimal:
|
|
34
|
+
"""
|
|
35
|
+
Get the value for a specific date. If the date does not exist, return the default value.
|
|
36
|
+
The date should be in the format yyyy-mm-dd.
|
|
37
|
+
If the value is None, return the default value.
|
|
38
|
+
"""
|
|
39
|
+
return self.data.get(key) or default
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, key) -> Decimal | None:
|
|
42
|
+
return self.data.get(key, None)
|
|
43
|
+
|
|
44
|
+
def __setitem__(self, key: str, value) -> None:
|
|
45
|
+
self.data[key] = value
|
|
46
|
+
|
|
47
|
+
def crop(self, start: str | None = None, end: str | None = None) -> "DateDict":
|
|
48
|
+
"""
|
|
49
|
+
Crop the graph data to a specific range defined by start and end.
|
|
50
|
+
If any of the parameters is None, it will not filter by that parameter.
|
|
51
|
+
"""
|
|
52
|
+
if start is None and end is None:
|
|
53
|
+
return self
|
|
54
|
+
return DateDict(
|
|
55
|
+
{
|
|
56
|
+
k: v
|
|
57
|
+
for k, v in self.data.items()
|
|
58
|
+
if (start is None or k >= start) and (end is None or k <= end)
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def sum(self: "DateDict") -> Decimal:
|
|
63
|
+
"""
|
|
64
|
+
Calculate the sum of all values in the graph.
|
|
65
|
+
If a value is None, it is treated as zero.
|
|
66
|
+
"""
|
|
67
|
+
return Decimal(
|
|
68
|
+
sum(value if value is not None else _Z for value in self.data.values())
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def __add__(self, other: "Decimal | DateDict") -> "DateDict":
|
|
72
|
+
if isinstance(other, Decimal):
|
|
73
|
+
return DateDict(
|
|
74
|
+
{
|
|
75
|
+
k: (v + other if v is not None else None)
|
|
76
|
+
for k, v in self.data.items()
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
return DateDict(
|
|
81
|
+
{
|
|
82
|
+
k: ((v + (other.data.get(k) or _Z) if v is not None else None))
|
|
83
|
+
for k, v in self.data.items()
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def __mul__(self, other: "Decimal | DateDict") -> "DateDict":
|
|
88
|
+
if isinstance(other, Decimal):
|
|
89
|
+
return DateDict(
|
|
90
|
+
{
|
|
91
|
+
k: (v * other if v is not None else None)
|
|
92
|
+
for k, v in self.data.items()
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
elif isinstance(other, DateDict):
|
|
96
|
+
return DateDict(
|
|
97
|
+
{
|
|
98
|
+
k: ((v * (other.data.get(k) or _Z) if v is not None else None))
|
|
99
|
+
for k, v in self.data.items()
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def __sub__(self, other: "Decimal | DateDict") -> "DateDict":
|
|
104
|
+
return self + (other * Decimal(-1))
|
|
105
|
+
|
|
106
|
+
def __truediv__(self, other: "Decimal | DateDict") -> "DateDict":
|
|
107
|
+
if isinstance(other, Decimal):
|
|
108
|
+
return DateDict(
|
|
109
|
+
{
|
|
110
|
+
k: (v / other if v is not None else None)
|
|
111
|
+
for k, v in self.data.items()
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
elif isinstance(other, DateDict):
|
|
115
|
+
return DateDict(
|
|
116
|
+
{
|
|
117
|
+
k: ((v / (other.data.get(k) or _Z) if v is not None else None))
|
|
118
|
+
for k, v in self.data.items()
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def to_dict(self) -> dict[str, Decimal | None]:
|
|
123
|
+
"""
|
|
124
|
+
Convert the graph data to a dictionary.
|
|
125
|
+
This is useful for serialization or returning as a response.
|
|
126
|
+
"""
|
|
127
|
+
return self.data.copy()
|
|
128
|
+
|
|
129
|
+
def average(self) -> Decimal:
|
|
130
|
+
"""
|
|
131
|
+
Calculate the average of all values in the graph.
|
|
132
|
+
If there are no valid values, return Decimal(0).
|
|
133
|
+
"""
|
|
134
|
+
valid_values = [v for v in self.data.values() if v is not None]
|
|
135
|
+
if not valid_values:
|
|
136
|
+
return _Z
|
|
137
|
+
return Decimal(sum(valid_values)) / len(valid_values)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
from .common import _to_decimal, _Z
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class YearDict:
|
|
7
|
+
def __init__(self, start_year=2025, end_year=2025, initial_value: Decimal | int | float = _Z):
|
|
8
|
+
self.start_year: int = start_year
|
|
9
|
+
self.end_year: int = end_year
|
|
10
|
+
iv: Decimal = _to_decimal(initial_value)
|
|
11
|
+
self.data: dict[int, Decimal] = {y: iv for y in range(self.start_year, self.end_year + 1)}
|
|
12
|
+
|
|
13
|
+
def __getitem__(self, year: int) -> Decimal:
|
|
14
|
+
return self.data[year]
|
|
15
|
+
|
|
16
|
+
def __setitem__(self, year: int, value):
|
|
17
|
+
self.data[int(year)] = _to_decimal(value)
|
|
18
|
+
|
|
19
|
+
def override(self, data: dict[int, Decimal | float | int | str]) -> "YearDict":
|
|
20
|
+
if not data:
|
|
21
|
+
raise ValueError("Data cannot be empty.")
|
|
22
|
+
ys = sorted(data.keys())
|
|
23
|
+
# enforce contiguous coverage
|
|
24
|
+
if ys != list(range(ys[0], ys[-1] + 1)):
|
|
25
|
+
raise ValueError("Data must cover all years in the contiguous range.")
|
|
26
|
+
self.start_year, self.end_year = ys[0], ys[-1] # inclusive
|
|
27
|
+
self.data = {
|
|
28
|
+
y: _to_decimal(data[y]) for y in range(self.start_year, self.end_year + 1)
|
|
29
|
+
}
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def fit(self, start_year: int, end_year: int, initial_value: Decimal = _Z):
|
|
33
|
+
self.start_year, self.end_year = int(start_year), int(end_year)
|
|
34
|
+
iv = _to_decimal(initial_value)
|
|
35
|
+
self.data = {
|
|
36
|
+
y: _to_decimal(self.data[y]) if y in self.data else iv
|
|
37
|
+
for y in range(self.start_year, self.end_year + 1)
|
|
38
|
+
}
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def non_negative(self) -> "YearDict":
|
|
42
|
+
out = YearDict(self.start_year, self.end_year)
|
|
43
|
+
out.data = {y: (v if v >= _Z else _Z) for y, v in self.data.items()}
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
def sum(
|
|
47
|
+
self, start_year: int | None = None, end_year: int | None = None
|
|
48
|
+
) -> Decimal:
|
|
49
|
+
sy = self.start_year if start_year is None else int(start_year)
|
|
50
|
+
ey = self.end_year if end_year is None else int(end_year)
|
|
51
|
+
return sum((self.data[y] for y in range(sy, ey + 1) if y in self.data), _Z)
|
|
52
|
+
|
|
53
|
+
def __mul__(self, other):
|
|
54
|
+
result = YearDict(self.start_year, self.end_year)
|
|
55
|
+
if isinstance(other, (int, float, Decimal)):
|
|
56
|
+
result.data = {
|
|
57
|
+
year: Decimal(value) * Decimal(str(other))
|
|
58
|
+
for year, value in self.data.items()
|
|
59
|
+
}
|
|
60
|
+
elif isinstance(other, YearDict):
|
|
61
|
+
result.data = {
|
|
62
|
+
year: Decimal(self.data[year]) * Decimal(other.data[year])
|
|
63
|
+
for year in self.data.keys() & other.data.keys()
|
|
64
|
+
}
|
|
65
|
+
else:
|
|
66
|
+
return NotImplemented
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
def __rmul__(self, other):
|
|
70
|
+
return self.__mul__(other)
|
|
71
|
+
|
|
72
|
+
def __add__(self, other):
|
|
73
|
+
result = YearDict(self.start_year, self.end_year)
|
|
74
|
+
if isinstance(other, (int, float, Decimal)):
|
|
75
|
+
result.data = {
|
|
76
|
+
year: value + Decimal(str(other)) for year, value in self.data.items()
|
|
77
|
+
}
|
|
78
|
+
elif isinstance(other, YearDict):
|
|
79
|
+
result.data = {
|
|
80
|
+
year: Decimal(self.data[year]) + Decimal(other.data[year])
|
|
81
|
+
for year in self.data.keys() & other.data.keys()
|
|
82
|
+
}
|
|
83
|
+
else:
|
|
84
|
+
return NotImplemented
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
def __radd__(self, other):
|
|
88
|
+
return self.__add__(other)
|
|
89
|
+
|
|
90
|
+
def __sub__(self, other):
|
|
91
|
+
return self.__add__(-1 * other)
|
|
92
|
+
|
|
93
|
+
def __str__(self):
|
|
94
|
+
return "\n".join(f"{y}: {v}" for y, v in sorted(self.data.items()))
|
|
95
|
+
|
|
96
|
+
def __repr__(self):
|
|
97
|
+
return f"{self.data!r}"
|
|
98
|
+
|
|
99
|
+
def to_array(self):
|
|
100
|
+
return [self.data[y] for y in range(self.start_year, self.end_year + 1)]
|
|
101
|
+
|
|
102
|
+
def to_dict(self):
|
|
103
|
+
return dict(self.data)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_Z = Decimal("0")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _to_decimal(x) -> Decimal:
|
|
8
|
+
if isinstance(x, Decimal):
|
|
9
|
+
return x
|
|
10
|
+
if isinstance(x, (int, str)):
|
|
11
|
+
return Decimal(x)
|
|
12
|
+
if isinstance(x, float):
|
|
13
|
+
return Decimal(str(x))
|
|
14
|
+
raise TypeError(f"Unsupported type for Decimal: {type(x)}")
|