gazpar2haws 0.2.0b1__py3-none-any.whl → 0.3.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.
gazpar2haws/model.py ADDED
@@ -0,0 +1,235 @@
1
+ from datetime import date
2
+ from enum import Enum
3
+ from pathlib import Path
4
+ from typing import Generic, Optional, TypeVar
5
+
6
+ from pydantic import BaseModel, DirectoryPath, EmailStr, SecretStr, model_validator
7
+ from pydantic_extra_types.timezone_name import TimeZoneName
8
+
9
+ from gazpar2haws.date_array import DateArray
10
+
11
+
12
+ # ----------------------------------
13
+ class LoggingLevel(str, Enum):
14
+ DEBUG = "debug"
15
+ INFO = "info"
16
+ WARNING = "warning"
17
+ ERROR = "error"
18
+ CRITICAL = "critical"
19
+
20
+
21
+ # ----------------------------------
22
+ class TimeUnit(str, Enum):
23
+ DAY = "day"
24
+ WEEK = "week"
25
+ MONTH = "month"
26
+ YEAR = "year"
27
+
28
+
29
+ # ----------------------------------
30
+ class PriceUnit(str, Enum):
31
+ EURO = "€"
32
+ CENT = "¢"
33
+
34
+
35
+ # ----------------------------------
36
+ class QuantityUnit(str, Enum):
37
+ MWH = "MWh"
38
+ KWH = "kWh"
39
+ WH = "Wh"
40
+ M3 = "m³"
41
+ LITER = "l"
42
+
43
+
44
+ # ----------------------------------
45
+ class Logging(BaseModel):
46
+ file: str
47
+ console: bool
48
+ level: LoggingLevel
49
+ format: str
50
+
51
+
52
+ # ----------------------------------
53
+ class Device(BaseModel):
54
+ name: str
55
+ data_source: str = "json"
56
+ tmp_dir: DirectoryPath = DirectoryPath("/tmp")
57
+ as_of_date: date = date.today()
58
+ username: Optional[EmailStr] = None
59
+ password: Optional[SecretStr] = None
60
+ pce_identifier: Optional[SecretStr] = None
61
+ timezone: TimeZoneName = TimeZoneName("Europe/Paris")
62
+ last_days: int = 365
63
+ reset: bool = False
64
+
65
+ @model_validator(mode="after")
66
+ def validate_properties(self):
67
+ if self.data_source not in ["json", "excel", "test"]:
68
+ raise ValueError(f"Invalid data_source{self.data_source} (expected values: json, excel, test)")
69
+ if self.data_source != "test" and self.username is None:
70
+ raise ValueError("Missing username")
71
+ if self.data_source != "test" and self.password is None:
72
+ raise ValueError("Missing password")
73
+ if self.data_source != "test" and self.pce_identifier is None:
74
+ raise ValueError("Missing pce_identifier")
75
+ if self.data_source == "excel" and self.tmp_dir is None or not Path(self.tmp_dir).is_dir():
76
+ raise ValueError(f"Invalid tmp_dir {self.tmp_dir}")
77
+ return self
78
+
79
+
80
+ # ----------------------------------
81
+ class Grdf(BaseModel):
82
+ scan_interval: Optional[int] = 480
83
+ devices: list[Device]
84
+
85
+
86
+ # ----------------------------------
87
+ class HomeAssistant(BaseModel):
88
+ host: str
89
+ port: int
90
+ endpoint: str = "/api/websocket"
91
+ token: SecretStr
92
+
93
+
94
+ # ----------------------------------
95
+ class Period(BaseModel):
96
+ start_date: date
97
+ end_date: Optional[date] = None
98
+
99
+
100
+ # ----------------------------------
101
+ class Value(Period):
102
+ value: float
103
+
104
+
105
+ # ----------------------------------
106
+ class ValueArray(Period):
107
+ name: Optional[str] = None
108
+ value_array: Optional[DateArray] = None
109
+
110
+ @model_validator(mode="after")
111
+ def set_value_array(self):
112
+ if self.value_array is None:
113
+ self.value_array = DateArray(
114
+ name=self.name, start_date=self.start_date, end_date=self.end_date
115
+ ) # pylint: disable=attribute-defined-outside-init
116
+ return self
117
+
118
+
119
+ # ----------------------------------
120
+ class Vat(BaseModel):
121
+ id: str
122
+
123
+
124
+ # ----------------------------------
125
+ class VatRate(Vat, Value):
126
+ pass
127
+
128
+
129
+ # ----------------------------------
130
+ class VatRateArray(Vat, ValueArray):
131
+ pass
132
+
133
+
134
+ # ----------------------------------
135
+ # Define type variables
136
+ ValueUnit = TypeVar("ValueUnit")
137
+ BaseUnit = TypeVar("BaseUnit")
138
+
139
+
140
+ # ----------------------------------
141
+ class Unit(BaseModel, Generic[ValueUnit, BaseUnit]):
142
+ value_unit: Optional[ValueUnit] = None
143
+ base_unit: Optional[BaseUnit] = None
144
+
145
+
146
+ # ----------------------------------
147
+ class Price(Unit[ValueUnit, BaseUnit]): # pylint: disable=too-few-public-methods
148
+ vat_id: Optional[str] = None
149
+
150
+
151
+ # ----------------------------------
152
+ class PriceValue(Price[ValueUnit, BaseUnit], Value):
153
+ pass
154
+
155
+
156
+ # ----------------------------------
157
+ class PriceValueArray(Price[ValueUnit, BaseUnit], ValueArray):
158
+ pass
159
+
160
+
161
+ # ----------------------------------
162
+ class ConsumptionPriceArray(PriceValueArray[PriceUnit, QuantityUnit]): # pylint: disable=too-few-public-methods
163
+ pass
164
+
165
+
166
+ # ----------------------------------
167
+ class SubscriptionPriceArray(PriceValueArray[PriceUnit, TimeUnit]): # pylint: disable=too-few-public-methods
168
+ pass
169
+
170
+
171
+ # ----------------------------------
172
+ class TransportPriceArray(PriceValueArray[PriceUnit, TimeUnit]): # pylint: disable=too-few-public-methods
173
+ pass
174
+
175
+
176
+ # ----------------------------------
177
+ class EnergyTaxesPriceArray(PriceValueArray[PriceUnit, QuantityUnit]): # pylint: disable=too-few-public-methods
178
+ pass
179
+
180
+
181
+ # ----------------------------------
182
+ class Pricing(BaseModel):
183
+ vat: Optional[list[VatRate]] = None
184
+ consumption_prices: list[PriceValue[PriceUnit, QuantityUnit]]
185
+ subscription_prices: Optional[list[PriceValue[PriceUnit, TimeUnit]]] = None
186
+ transport_prices: Optional[list[PriceValue[PriceUnit, TimeUnit]]] = None
187
+ energy_taxes: Optional[list[PriceValue[PriceUnit, QuantityUnit]]] = None
188
+
189
+ @model_validator(mode="before")
190
+ @classmethod
191
+ def propagates_properties(cls, values):
192
+ for price_list in [
193
+ "consumption_prices",
194
+ "subscription_prices",
195
+ "transport_prices",
196
+ "energy_taxes",
197
+ ]:
198
+ prices = values.get(price_list, [])
199
+
200
+ if len(prices) == 0:
201
+ continue
202
+
203
+ if "start_date" not in prices[0]:
204
+ raise ValueError(f"Missing start_date in first element of {price_list}")
205
+ if "value_unit" not in prices[0]:
206
+ prices[0]["value_unit"] = "€"
207
+ if "base_unit" not in prices[0]:
208
+ if price_list in ["consumption_prices", "energy_taxes"]:
209
+ prices[0]["base_unit"] = "kWh"
210
+ else:
211
+ raise ValueError(
212
+ "Missing base_unit in first element of ['transport_prices', 'subscription_prices']"
213
+ )
214
+
215
+ for i in range(len(prices) - 1):
216
+ if "end_date" not in prices[i]:
217
+ prices[i]["end_date"] = prices[i + 1]["start_date"]
218
+ if "value_unit" not in prices[i + 1]:
219
+ prices[i + 1]["value_unit"] = prices[i]["value_unit"]
220
+ if "base_unit" not in prices[i + 1]:
221
+ prices[i + 1]["base_unit"] = prices[i]["base_unit"]
222
+ if "vat_id" not in prices[i + 1] and "vat_id" in prices[i]:
223
+ prices[i + 1]["vat_id"] = prices[i]["vat_id"]
224
+
225
+ return values
226
+
227
+
228
+ # ----------------------------------
229
+ class ConsumptionQuantityArray(Unit[QuantityUnit, TimeUnit], ValueArray):
230
+ pass
231
+
232
+
233
+ # ----------------------------------
234
+ class CostArray(Unit[PriceUnit, TimeUnit], ValueArray):
235
+ pass