openadr3-client-gac-compliance 3.0.0a2__py3-none-any.whl → 3.0.3__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.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: openadr3-client-gac-compliance
3
+ Version: 3.0.3
4
+ Project-URL: Homepage, https://github.com/ElaadNL/openadr3-client
5
+ Project-URL: Repository, https://github.com/ElaadNL/openadr3-client
6
+ Project-URL: Bug Tracker, https://github.com/ElaadNL/openadr3-client/issues
7
+ Project-URL: Changelog, https://github.com/ElaadNL/openadr3-client/releases
8
+ Author-email: Nick van der Burgt <nick.van.der.burgt@elaad.nl>, Stijn van Houwelingen <stijn.van.houwelingen@elaad.nl>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE.md
11
+ Requires-Python: <4,>=3.12
12
+ Requires-Dist: openadr3-client<2.0.0,>=1.0.0a1
13
+ Requires-Dist: pycountry<25.0.0,>=24.6.1
14
+ Requires-Dist: pydantic<3.0.0,>=2.11.2
15
+ Description-Content-Type: text/markdown
16
+
17
+ <!--
18
+ SPDX-FileCopyrightText: Contributors to openadr3-client-gac-compliance <https://github.com/ElaadNL/openadr3-client-gac-compliance>
19
+
20
+ SPDX-License-Identifier: Apache-2.0
21
+ -->
22
+
23
+ [![CodeQL Advanced](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/codeql.yml/badge.svg)](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/codeql.yml)
24
+ [![Python Default CI](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/ci.yml/badge.svg)](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/ci.yml)
25
+ ![PYPI-DL](https://img.shields.io/pypi/dm/openadr3-client-gac-compliance?style=flat)
26
+ [![image](https://img.shields.io/pypi/v/openadr3-client-gac-compliance?label=pypi)](https://pypi.python.org/pypi/openadr3-client-gac-compliance)
27
+ ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FElaadNL%2Fopenadr3-client-gac-compliance%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
28
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
29
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
30
+
31
+ # OpenADR3 client
32
+
33
+ This repository contains a plugin for the [OpenADR3-client](https://github.com/ElaadNL/openadr3-client) library that adds additional Pydantic validators to the OpenADR3 domain models to ensure GAC compliance. Since GAC compliance is a superset of OpenADR3, adding validation rules on top of the OpenADR3 models is sufficient to ensure compliance.
34
+
35
+ Registering the plugin is done using the global ValidatorPluginRegistry class:
36
+
37
+ ```python
38
+ from openadr3_client.plugin import ValidatorPluginRegistry, ValidatorPlugin
39
+ from openadr3_client_gac_compliance.gac20.plugin import Gac20ValidatorPlugin
40
+
41
+ ValidatorPluginRegistry.register_plugin(
42
+ Gac20ValidatorPlugin().setup()
43
+ )
44
+ ```
45
+
46
+ ## License
47
+
48
+ This project is licensed under the Apache-2.0 - see LICENSE for details.
49
+
50
+ ## Licenses third-party libraries
51
+
52
+ This project includes third-party libraries, which are licensed under their own respective Open-Source licenses.
53
+ SPDX-License-Identifier headers are used to show which license is applicable. The concerning license files can be found in the LICENSES directory.
54
+
55
+ ---
56
+
57
+ ## About ElaadNL
58
+
59
+ OpenADRGUI is built by ElaadNL, with the goal of using it for both internal projects as well as those of stakeholders.
60
+
61
+ ElaadNL is a Dutch research institute founded and funded by the Dutch District Service Operators (DSOs). ElaadNL was originally tasked by the DSOs to kickstart and foster the adoption of Electric Vehicles by installing the first Dutch charging stations, as well as monitoring the effects EVs have on the grid.
62
+
63
+ A major result of this pioneering work, was the creation of the Open Charge Point Protocol (OCPP), which today is the de-facto standard for CPOs to communicate with and manage their chargepoints. The protocol is now managed in a spin-off organization: the Open Charge Alliance, which is still closely connected with ElaadNL.
64
+
65
+ Whereas ElaadNL initially focused mainly on EVs, it has now expanded its mandate to include residential energy use with the goal of increasing the adoption of demand response measures. The reason for this move is to improve efficient use of the resources of the DSO in order to reduce grid congestion, which is a major problem challenge for the Dutch DSOs as well as society as a whole.
@@ -0,0 +1,4 @@
1
+ openadr3_client_gac_compliance-3.0.3.dist-info/METADATA,sha256=iAprRN-ooTTzZV1SEaVDqcBYUPY2HKU_wR-VeooCZ2w,4287
2
+ openadr3_client_gac_compliance-3.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
3
+ openadr3_client_gac_compliance-3.0.3.dist-info/licenses/LICENSE.md,sha256=uRIdtYuOWtiQI6jCQ-wjgAemRbPMTaHtWKfJi6E8H5g,10251
4
+ openadr3_client_gac_compliance-3.0.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -73,4 +73,4 @@ Unless required by applicable law or agreed to in writing, software
73
73
  distributed under the License is distributed on an "AS IS" BASIS,
74
74
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
75
75
  See the License for the specific language governing permissions and
76
- limitations under the License.
76
+ limitations under the License.
@@ -1,22 +0,0 @@
1
- """
2
- OpenADR3 GAC Compliance Plugin.
3
-
4
- This package provides validation plugins for OpenADR3 models to ensure compliance
5
- with the Grid Aware Charging (GAC) specification.
6
-
7
- The main entry point is the Gac20ValidatorPlugin which can be registered
8
- with the OpenADR3 client's validator plugin registry.
9
-
10
- Example:
11
- ```python
12
- from openadr3_client.plugin import ValidatorPluginRegistry
13
- from openadr3_client_gac_compliance.plugin import Gac20ValidatorPlugin
14
- from openadr3_client_gac_compliance.gac20.gac_plugin import GacVersion
15
-
16
- # Register the GAC validation plugin
17
- ValidatorPluginRegistry.register_plugin(
18
- Gac20ValidatorPlugin.setup(gac_version=GacVersion.VERSION_2_0)
19
- )
20
- ```
21
-
22
- """
@@ -1,4 +0,0 @@
1
- # This module imports all the GAC 3.0 compliance validators.
2
-
3
- import openadr3_client_gac_compliance.gac20.event_gac_compliant
4
- import openadr3_client_gac_compliance.gac20.program_gac_compliant # noqa: F401
@@ -1,402 +0,0 @@
1
- """
2
- Module which implements GAC compliance validators for the event OpenADR3 types.
3
-
4
- This module validates all the object constraints and requirements on the OpenADR3 events resource
5
- as specified in the Grid aware charging (GAC) specification.
6
-
7
- There is one requirement that is not validated here, as it cannot be validated through the scope of the
8
- pydantic validators. Namely, the requirement that a safe mode event MUST be present in a program.
9
-
10
- As the pydantic validator works on the scope of a single Event Object, it is not possible to validate
11
- that a safe mode event is present in a program. And it cannot be validated on the Program object,
12
- as the program object does not contain the events, these are stored separately in the VTN.
13
- """
14
-
15
- import re
16
- from itertools import pairwise
17
-
18
- from openadr3_client.models.event.event import Event
19
- from openadr3_client.models.event.event_payload import EventPayloadType
20
- from pydantic_core import InitErrorDetails, PydanticCustomError
21
-
22
- INTERVAL_PERIOD_ERROR_MESSAGE = "'interval_period' must either be set on the event-level, or for each interval."
23
-
24
-
25
- def _continuous_or_separated(self: Event) -> list[InitErrorDetails]:
26
- """
27
- Validates that events have consistent interval definitions GAC compliant.
28
-
29
- The Grid aware charging (GAC) specification allows for two types of (mutually exclusive)
30
- interval definitions:
31
-
32
- 1. Continuous
33
- 2. Separated
34
-
35
- The continuous implementation can be used when all intervals have the same duration.
36
- In this case, only the top-level intervalPeriod of the event may be used, and the intervalPeriods
37
- of the individual intervals must be None.
38
-
39
- In the separated implementation, the intervalPeriods must be set on each individual intervals,
40
- and the top-level intervalPeriod of the event must be None. This separated implementation allows events to have differing
41
- durations.
42
- """ # noqa: E501
43
- validation_errors: list[InitErrorDetails] = []
44
-
45
- intervals = self.intervals or ()
46
-
47
- if self.interval_period is None:
48
- # interval period not set at top level of the event.
49
- # Ensure that all intervals have the interval_period defined, to comply with the GAC specification.
50
- undefined_intervals_period = [i for i in intervals if i.interval_period is None]
51
- if undefined_intervals_period:
52
- validation_errors.append(
53
- InitErrorDetails(
54
- type=PydanticCustomError(
55
- "value_error",
56
- INTERVAL_PERIOD_ERROR_MESSAGE,
57
- ),
58
- loc=("intervals",),
59
- input=self.intervals,
60
- ctx={},
61
- )
62
- )
63
- else:
64
- # interval period set at top level of the event.
65
- # Ensure that all intervals do not have the interval_period defined, to comply with the GAC specification.
66
- duplicate_interval_period = [i for i in intervals if i.interval_period is not None]
67
- if duplicate_interval_period:
68
- validation_errors.append(
69
- InitErrorDetails(
70
- type=PydanticCustomError(
71
- "value_error",
72
- INTERVAL_PERIOD_ERROR_MESSAGE,
73
- ),
74
- loc=("intervals",),
75
- input=self.intervals,
76
- ctx={},
77
- )
78
- )
79
-
80
- return validation_errors
81
-
82
-
83
- def _targets_compliant(self: Event) -> list[InitErrorDetails]:
84
- """
85
- Validates that the targets of the event are GAC compliant.
86
-
87
- The following constraints are enforced for targets:
88
-
89
- - The event must contain a POWER_SERVICE_LOCATION target.
90
- - The POWER_SERVICE_LOCATION target value must be a list of 'EAN18' values.
91
- - The event must contain a VEN_NAME target.
92
- - The VEN_NAME target value must be a list of 'VEN name' values (between 1 and 128 characters).
93
- """
94
- validation_errors: list[InitErrorDetails] = []
95
- targets = self.targets or ()
96
-
97
- power_service_locations = [t for t in targets if t.type == "POWER_SERVICE_LOCATION"]
98
- ven_names = [t for t in targets if t.type == "VEN_NAME"]
99
-
100
- if not power_service_locations:
101
- validation_errors.append(
102
- InitErrorDetails(
103
- type=PydanticCustomError(
104
- "value_error",
105
- "The event must contain a POWER_SERVICE_LOCATION target.",
106
- ),
107
- loc=("targets",),
108
- input=self.targets,
109
- ctx={},
110
- )
111
- )
112
-
113
- if not ven_names:
114
- validation_errors.append(
115
- InitErrorDetails(
116
- type=PydanticCustomError(
117
- "value_error",
118
- "The event must contain a VEN_NAME target.",
119
- ),
120
- loc=("targets",),
121
- input=self.targets,
122
- ctx={},
123
- )
124
- )
125
-
126
- if len(power_service_locations) > 1:
127
- validation_errors.append(
128
- InitErrorDetails(
129
- type=PydanticCustomError(
130
- "value_error",
131
- "The event must contain exactly one POWER_SERVICE_LOCATION target.",
132
- ),
133
- loc=("targets",),
134
- input=self.targets,
135
- ctx={},
136
- )
137
- )
138
-
139
- if len(ven_names) > 1:
140
- validation_errors.append(
141
- InitErrorDetails(
142
- type=PydanticCustomError(
143
- "value_error",
144
- "The event must contain exactly one VEN_NAME target.",
145
- ),
146
- loc=("targets",),
147
- input=self.targets,
148
- ctx={},
149
- )
150
- )
151
-
152
- if power_service_locations and ven_names and len(power_service_locations) == 1 and len(ven_names) == 1:
153
- power_service_location = power_service_locations[0]
154
- ven_name = ven_names[0]
155
-
156
- if len(power_service_location.values) == 0:
157
- validation_errors.append(
158
- InitErrorDetails(
159
- type=PydanticCustomError(
160
- "value_error",
161
- "The POWER_SERVICE_LOCATION target value may not be empty.",
162
- ),
163
- loc=("targets",),
164
- input=self.targets,
165
- ctx={},
166
- )
167
- )
168
-
169
- if not all(re.fullmatch(r"^\d{18}$", v) for v in power_service_location.values):
170
- validation_errors.append(
171
- InitErrorDetails(
172
- type=PydanticCustomError(
173
- "value_error",
174
- "The POWER_SERVICE_LOCATION target value must be a list of 'EAN18' values.",
175
- ),
176
- loc=("targets",),
177
- input=self.targets,
178
- ctx={},
179
- )
180
- )
181
-
182
- if len(ven_name.values) == 0:
183
- validation_errors.append(
184
- InitErrorDetails(
185
- type=PydanticCustomError(
186
- "value_error",
187
- "The VEN_NAME target value may not be empty.",
188
- ),
189
- loc=("targets",),
190
- input=self.targets,
191
- ctx={},
192
- )
193
- )
194
-
195
- if not all(1 <= len(v) <= 128 for v in ven_name.values): # noqa: PLR2004
196
- validation_errors.append(
197
- InitErrorDetails(
198
- type=PydanticCustomError(
199
- "value_error",
200
- "The VEN_NAME target value must be a list of 'VEN name' values (between 1 and 128 characters).",
201
- ),
202
- loc=("targets",),
203
- input=self.targets,
204
- ctx={},
205
- )
206
- )
207
-
208
- return validation_errors
209
-
210
-
211
- def _payload_descriptors_gac_compliant(
212
- self: Event,
213
- ) -> list[InitErrorDetails]:
214
- """
215
- Validates that the payload descriptor is GAC compliant.
216
-
217
- The following constraints are enforced for payload descriptors:
218
-
219
- - The event interval must contain exactly one payload descriptor.
220
- - The payload descriptor must have a payload type of 'IMPORT_CAPACITY_LIMIT'
221
- - The payload descriptor must have a units of 'KW' (case sensitive).
222
- """
223
- validation_errors: list[InitErrorDetails] = []
224
-
225
- if self.payload_descriptors is None:
226
- validation_errors.append(
227
- InitErrorDetails(
228
- type=PydanticCustomError(
229
- "value_error",
230
- "The event must have a payload descriptor.",
231
- ),
232
- loc=("payload_descriptors",),
233
- input=self.payload_descriptors,
234
- ctx={},
235
- )
236
- )
237
-
238
- if self.payload_descriptors is not None:
239
- if len(self.payload_descriptors) != 1:
240
- validation_errors.append(
241
- InitErrorDetails(
242
- type=PydanticCustomError(
243
- "value_error",
244
- "The event must have exactly one payload descriptor.",
245
- ),
246
- loc=("payload_descriptors",),
247
- input=self.payload_descriptors,
248
- ctx={},
249
- )
250
- )
251
-
252
- payload_descriptors = self.payload_descriptors[0]
253
-
254
- if payload_descriptors.payload_type != EventPayloadType.IMPORT_CAPACITY_LIMIT:
255
- validation_errors.append(
256
- InitErrorDetails(
257
- type=PydanticCustomError(
258
- "value_error",
259
- "The payload descriptor must have a payload type of 'IMPORT_CAPACITY_LIMIT'.",
260
- ),
261
- loc=("payload_descriptors",),
262
- input=self.payload_descriptors,
263
- ctx={},
264
- )
265
- )
266
-
267
- if payload_descriptors.units != "KW":
268
- validation_errors.append(
269
- InitErrorDetails(
270
- type=PydanticCustomError(
271
- "value_error",
272
- "The payload descriptor must have a units of 'KW' (case sensitive).",
273
- ),
274
- loc=("payload_descriptors",),
275
- input=self.payload_descriptors,
276
- ctx={},
277
- )
278
- )
279
-
280
- return validation_errors
281
-
282
-
283
- def _event_interval_gac_compliant(self: Event) -> list[InitErrorDetails]:
284
- """
285
- Validates that the event interval is GAC compliant.
286
-
287
- The following constraints are enforced for event intervals:
288
-
289
- - The event interval must have an id value that is strictly increasing.
290
- - The event interval must have exactly one payload.
291
- - The payload of the event interval must have a type of 'IMPORT_CAPACITY_LIMIT'
292
- """
293
- validation_errors: list[InitErrorDetails] = []
294
-
295
- if not self.intervals:
296
- validation_errors.append(
297
- InitErrorDetails(
298
- type=PydanticCustomError(
299
- "value_error",
300
- "The event must have at least one interval.",
301
- ),
302
- loc=("intervals",),
303
- input=self.intervals,
304
- ctx={},
305
- )
306
- )
307
-
308
- if not all(curr.id > prev.id for prev, curr in pairwise(self.intervals)):
309
- validation_errors.append(
310
- InitErrorDetails(
311
- type=PydanticCustomError(
312
- "value_error",
313
- "The event interval must have an id value that is strictly increasing.",
314
- ),
315
- loc=("intervals",),
316
- input=self.intervals,
317
- ctx={},
318
- )
319
- )
320
-
321
- for interval in self.intervals:
322
- if interval.payloads is None:
323
- validation_errors.append(
324
- InitErrorDetails(
325
- type=PydanticCustomError(
326
- "value_error",
327
- "The event interval must have a payload.",
328
- ),
329
- loc=("intervals",),
330
- input=self.intervals,
331
- ctx={},
332
- )
333
- )
334
-
335
- if len(interval.payloads) != 1:
336
- validation_errors.append(
337
- InitErrorDetails(
338
- type=PydanticCustomError(
339
- "value_error",
340
- "The event interval must have exactly one payload.",
341
- ),
342
- loc=("intervals",),
343
- input=self.intervals,
344
- ctx={},
345
- )
346
- )
347
- else:
348
- payload = interval.payloads[0]
349
-
350
- if payload.type != EventPayloadType.IMPORT_CAPACITY_LIMIT:
351
- validation_errors.append(
352
- InitErrorDetails(
353
- type=PydanticCustomError(
354
- "value_error",
355
- "The event interval payload must have a payload type of 'IMPORT_CAPACITY_LIMIT'.",
356
- ),
357
- loc=("intervals",),
358
- input=self.intervals,
359
- ctx={},
360
- )
361
- )
362
-
363
- return validation_errors
364
-
365
-
366
- def validate_event_gac_compliant(event: Event) -> list[InitErrorDetails] | None:
367
- """
368
- Validates that events are GAC compliant.
369
-
370
- The following constraints are enforced for events:
371
-
372
- - The event must not have a priority set.
373
- - The event must have either a continuous or separated interval definition.
374
- """
375
- validation_errors: list[InitErrorDetails] = []
376
-
377
- if event.priority is not None:
378
- validation_errors.append(
379
- InitErrorDetails(
380
- type=PydanticCustomError(
381
- "value_error",
382
- "The event must not have a priority set for GAC 2.0 compliance",
383
- ),
384
- loc=("priority",),
385
- input=event.priority,
386
- ctx={},
387
- )
388
- )
389
-
390
- errors = _continuous_or_separated(event)
391
- validation_errors.extend(errors)
392
-
393
- errors = _targets_compliant(event)
394
- validation_errors.extend(errors)
395
-
396
- errors = _payload_descriptors_gac_compliant(event)
397
- validation_errors.extend(errors)
398
-
399
- errors = _event_interval_gac_compliant(event)
400
- validation_errors.extend(errors)
401
-
402
- return validation_errors if validation_errors else None
@@ -1,43 +0,0 @@
1
- """GAC compliance plugin for OpenADR3 client."""
2
-
3
- from typing import Any
4
-
5
- from openadr3_client.models.event.event import Event
6
- from openadr3_client.models.program.program import Program
7
- from openadr3_client.models.ven.ven import Ven
8
- from openadr3_client.plugin import ValidatorPlugin
9
-
10
- from openadr3_client_gac_compliance.gac20.event_gac_compliant import validate_event_gac_compliant
11
- from openadr3_client_gac_compliance.gac20.program_gac_compliant import validate_program_gac_compliant
12
- from openadr3_client_gac_compliance.gac20.ven_gac_compliant import validate_ven_gac_compliant
13
-
14
-
15
- class Gac20ValidatorPlugin(ValidatorPlugin):
16
- """Plugin that validates OpenADR3 models for GAC compliance."""
17
-
18
- def __init__(self) -> None:
19
- """Initialize the GAC validator plugin."""
20
- super().__init__()
21
-
22
- @staticmethod
23
- def setup(*_args: Any, **_kwargs: Any) -> "Gac20ValidatorPlugin": # noqa: ANN401
24
- """
25
- Set up the GAC validator plugin.
26
-
27
- Args:
28
- *args: Positional arguments (unused).
29
- **kwargs: Keyword arguments containing configuration.
30
- Expected keys:
31
- - gac_version: The GAC version to validate against.
32
-
33
- Returns:
34
- GacValidatorPlugin: Configured plugin instance.
35
-
36
- """
37
- plugin = Gac20ValidatorPlugin()
38
-
39
- plugin.register_model_validator(Event, validate_event_gac_compliant)
40
- plugin.register_model_validator(Program, validate_program_gac_compliant)
41
- plugin.register_model_validator(Ven, validate_ven_gac_compliant)
42
-
43
- return plugin
@@ -1,100 +0,0 @@
1
- """Module which implements GAC compliance validators for the program OpenADR3 types."""
2
-
3
- import re
4
-
5
- from openadr3_client.models.program.program import Program
6
- from pydantic_core import InitErrorDetails, PydanticCustomError
7
-
8
-
9
- def validate_program_gac_compliant(program: Program) -> list[InitErrorDetails] | None:
10
- """
11
- Validates that the program is GAC compliant.
12
-
13
- The following constraints are enforced for programs:
14
- - The program must have a retailer name
15
- - The retailer name must be between 2 and 128 characters long.
16
- - The program MUST have a programType.
17
- - The programType MUST equal "DSO_CPO_INTERFACE-x.x.x, where x.x.x is the version as defined in the GAC specification.
18
- - The program MUST have bindingEvents set to true.
19
-
20
- """ # noqa: E501
21
- validation_errors: list[InitErrorDetails] = []
22
-
23
- program_type_regex = (
24
- r"^DSO_CPO_INTERFACE-"
25
- r"(0|[1-9]\d*)\."
26
- r"(0|[1-9]\d*)\."
27
- r"(0|[1-9]\d*)"
28
- r"(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
29
- r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))"
30
- r"?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?"
31
- r"$"
32
- )
33
-
34
- if program.retailer_name is None:
35
- validation_errors.append(
36
- InitErrorDetails(
37
- type=PydanticCustomError(
38
- "value_error",
39
- "The program must have a retailer name.",
40
- ),
41
- loc=("retailer_name",),
42
- input=program.retailer_name,
43
- ctx={},
44
- )
45
- )
46
-
47
- if program.retailer_name is not None and (
48
- len(program.retailer_name) < 2 or len(program.retailer_name) > 128 # noqa: PLR2004
49
- ):
50
- validation_errors.append(
51
- InitErrorDetails(
52
- type=PydanticCustomError(
53
- "value_error",
54
- "The retailer name must be between 2 and 128 characters long.",
55
- ),
56
- loc=("retailer_name",),
57
- input=program.retailer_name,
58
- ctx={},
59
- )
60
- )
61
-
62
- if program.program_type is None:
63
- validation_errors.append(
64
- InitErrorDetails(
65
- type=PydanticCustomError(
66
- "value_error",
67
- "The program must have a program type.",
68
- ),
69
- loc=("program_type",),
70
- input=program.program_type,
71
- ctx={},
72
- )
73
- )
74
- if program.program_type is not None and not re.fullmatch(program_type_regex, program.program_type):
75
- validation_errors.append(
76
- InitErrorDetails(
77
- type=PydanticCustomError(
78
- "value_error",
79
- "The program type must follow the format DSO_CPO_INTERFACE-x.x.x.",
80
- ),
81
- loc=("program_type",),
82
- input=program.program_type,
83
- ctx={},
84
- )
85
- )
86
-
87
- if program.binding_events is False:
88
- validation_errors.append(
89
- InitErrorDetails(
90
- type=PydanticCustomError(
91
- "value_error",
92
- "The program must have bindingEvents set to true.",
93
- ),
94
- loc=("binding_events",),
95
- input=program.binding_events,
96
- ctx={},
97
- )
98
- )
99
-
100
- return validation_errors if validation_errors else None
@@ -1,49 +0,0 @@
1
- import re
2
-
3
- import pycountry
4
- from openadr3_client.models.ven.ven import Ven
5
- from pydantic_core import InitErrorDetails, PydanticCustomError
6
-
7
-
8
- def validate_ven_gac_compliant(ven: Ven) -> list[InitErrorDetails] | None:
9
- """
10
- Validates that the VEN is GAC compliant.
11
-
12
- The following constraints are enforced for VENs:
13
- - The VEN must have a VEN name
14
- - The VEN name must be an eMI3 identifier.
15
-
16
- """
17
- validation_errors: list[InitErrorDetails] = []
18
-
19
- emi3_identifier_regex = r"^[A-Z]{2}-?[A-Z0-9]{3}$"
20
-
21
- if not re.fullmatch(emi3_identifier_regex, ven.ven_name):
22
- validation_errors.append(
23
- InitErrorDetails(
24
- type=PydanticCustomError(
25
- "value_error",
26
- "The VEN name must be formatted as an eMI3 identifier.",
27
- ),
28
- loc=("ven_name",),
29
- input=ven.ven_name,
30
- ctx={},
31
- )
32
- )
33
-
34
- alpha_2_country = pycountry.countries.get(alpha_2=ven.ven_name[:2])
35
-
36
- if alpha_2_country is None:
37
- validation_errors.append(
38
- InitErrorDetails(
39
- type=PydanticCustomError(
40
- "value_error",
41
- "The first two characters of the VEN name must be a valid ISO 3166-1 alpha-2 country code.",
42
- ),
43
- loc=("ven_name",),
44
- input=ven.ven_name,
45
- ctx={},
46
- )
47
- )
48
-
49
- return validation_errors if validation_errors else None
@@ -1 +0,0 @@
1
- # This module imports all the GAC 3.1 compliance validators.
@@ -1,31 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: openadr3-client-gac-compliance
3
- Version: 3.0.0a2
4
- Summary:
5
- License-File: LICENSE.md
6
- Author: Nick van der Burgt
7
- Author-email: nick.van.der.burgt@elaad.nl
8
- Requires-Python: >=3.12, <4
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.12
11
- Classifier: Programming Language :: Python :: 3.13
12
- Classifier: Programming Language :: Python :: 3.14
13
- Requires-Dist: openadr3-client (>=1.0.0a1,<2.0.0)
14
- Requires-Dist: pycountry (>=24.6.1,<25.0.0)
15
- Requires-Dist: pydantic (>=2.11.2,<3.0.0)
16
- Description-Content-Type: text/markdown
17
-
18
- # OpenADR3 client
19
-
20
- This repository contains a plugin for the [OpenADR3-client](https://github.com/ElaadNL/openadr3-client) library that adds additional pydantic validators to the OpenADR3 domain models to ensure GAC compliance. Since GAC compliance is a superset of OpenADR3, adding validation rules on top of the OpenADR3 models is sufficient to ensure compliance.
21
-
22
- Registering the plugin is done using the global ValidatorPluginRegistry class:
23
-
24
- ```python
25
- from openadr3_client.plugin import ValidatorPluginRegistry, ValidatorPlugin
26
- from openadr3_client_gac_compliance.gac20.plugin import Gac20ValidatorPlugin
27
-
28
- ValidatorPluginRegistry.register_plugin(
29
- Gac20ValidatorPlugin().setup()
30
- )
31
- ```
@@ -1,11 +0,0 @@
1
- openadr3_client_gac_compliance/__init__.py,sha256=waVEfmDlohZjVky1ihZQC7rwoLO0hstSK1D8OWOrDmI,711
2
- openadr3_client_gac_compliance/gac20/__init__.py,sha256=8fRxykcK5POwFwKNq2KvbGh7Du_MvS_rIsEMfk6x-zI,206
3
- openadr3_client_gac_compliance/gac20/event_gac_compliant.py,sha256=uRFGV5PJMObeJCjuG1b0rjsj8zZX1gZukKS-U23GgLU,14412
4
- openadr3_client_gac_compliance/gac20/plugin.py,sha256=_2qfWXvvvRJ1jIe4TKtsEseLPqqzlHsYlJ-UQSJeliY,1589
5
- openadr3_client_gac_compliance/gac20/program_gac_compliant.py,sha256=aBet29uTUpfKNfJFICrXemT1PW6pXPVsK4ekh15nCLU,3420
6
- openadr3_client_gac_compliance/gac20/ven_gac_compliant.py,sha256=y6f2uNtGaJJ8MM-78XHlAikRpJa8w-s8vhIbHxzj7r4,1499
7
- openadr3_client_gac_compliance/gac21/__init__.py,sha256=GTu1EyBj2T72zT3MKPettcvP-DNCJ0p6oAMj3Z-08N0,61
8
- openadr3_client_gac_compliance-3.0.0a2.dist-info/METADATA,sha256=KEK0lvOAZ_ljUDxR7xO3D_cDaNF5fE3vQ2eWZ1oAW0Y,1299
9
- openadr3_client_gac_compliance-3.0.0a2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
10
- openadr3_client_gac_compliance-3.0.0a2.dist-info/licenses/LICENSE.md,sha256=NNNxKzhSK6afX-UaN8WZIVtMi5Tu8dVr6q3ciTSnH-g,10250
11
- openadr3_client_gac_compliance-3.0.0a2.dist-info/RECORD,,