datedict 0.1.1__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.

Potentially problematic release.


This version of datedict might be problematic. Click here for more details.

datedict/DateDict.py ADDED
@@ -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)
datedict/YearDict.py ADDED
@@ -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)
datedict/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .DateDict import DateDict
2
+ from .YearDict import YearDict
3
+
4
+ __all__ = ["DateDict", "YearDict"]
5
+ __version__ = "0.1.1"
datedict/common.py ADDED
@@ -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)}")
@@ -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
+ ```
@@ -0,0 +1,8 @@
1
+ datedict/DateDict.py,sha256=2jXhhR7j9cs6WN9pTtSbUC-xpMtx6hGy-1Lr-sV4Piw,4659
2
+ datedict/YearDict.py,sha256=DVSIG3eZ_v4sp7r9ybzXgfJaF_W7O-CO2z4IavzYlzA,3731
3
+ datedict/__init__.py,sha256=KnG-yvfCIx2MXNQ6gJ4QrZP4jZVYx84FUV9KnpQctu8,120
4
+ datedict/common.py,sha256=GQS1uxInOxhQI6dg2Aqxe8A_OIQ-L7R4WJbZJoDplmE,313
5
+ datedict-0.1.1.dist-info/METADATA,sha256=4T8ERj7yKNTFKj55eLB-QgreJbBppZ2fXylpT8cNOss,1985
6
+ datedict-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ datedict-0.1.1.dist-info/licenses/LICENSE,sha256=ULDgLM2c4o9DLMR-VEkeyuB-DRWBFX7VunJmlWShWxQ,1072
8
+ datedict-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.