openadr3-client-gac-compliance 3.0.1__tar.gz → 3.0.3__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.
@@ -0,0 +1,153 @@
1
+ # SPDX-FileCopyrightText: Contributors to openadr3-client-gac-compliance <https://github.com/ElaadNL/openadr3-client-gac-compliance>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ # MacOs specific metadata
6
+ .DS_Store
7
+
8
+ # Byte-compiled / optimized / DLL files
9
+ __pycache__/
10
+ **/__pycache__/
11
+ *.py[cod]
12
+ *$py.class
13
+
14
+ # C extensions
15
+ *.so
16
+
17
+ # Distribution / packaging
18
+ .Python
19
+ build/
20
+ develop-eggs/
21
+ dist/
22
+ downloads/
23
+ eggs/
24
+ .eggs/
25
+ .idea/
26
+ lib/
27
+ lib64/
28
+ parts/
29
+ sdist/
30
+ var/
31
+ wheels/
32
+ share/python-wheels/
33
+ *.egg-info/
34
+ .installed.cfg
35
+ *.egg
36
+ MANIFEST
37
+
38
+ # PyInstaller
39
+ # Usually these files are written by a python script from a template
40
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
41
+ *.manifest
42
+ *.spec
43
+
44
+ # Installer logs
45
+ pip-log.txt
46
+ pip-delete-this-directory.txt
47
+
48
+ # Unit test / coverage reports
49
+ htmlcov/
50
+ .tox/
51
+ .nox/
52
+ .coverage
53
+ .coverage.*
54
+ .cache
55
+ nosetests.xml
56
+ coverage.xml
57
+ *.cover
58
+ *.py,cover
59
+ .hypothesis/
60
+ .pytest_cache/
61
+ cover/
62
+
63
+ # Translations
64
+ *.mo
65
+ *.pot
66
+
67
+ # Django stuff:
68
+ *.log
69
+ local_settings.py
70
+ db.sqlite3
71
+ db.sqlite3-journal
72
+
73
+ # Flask stuff:
74
+ instance/
75
+ .webassets-cache
76
+
77
+ # Scrapy stuff:
78
+ .scrapy
79
+
80
+ # Sphinx documentation
81
+ docs/_build/
82
+
83
+ # PyBuilder
84
+ .pybuilder/
85
+ target/
86
+
87
+ # Jupyter Notebook
88
+ .ipynb_checkpoints
89
+
90
+ # IPython
91
+ profile_default/
92
+ ipython_config.py
93
+
94
+ # pyenv
95
+ # For a library or package, you might want to ignore these files since the code is
96
+ # intended to run in multiple environments; otherwise, check them in:
97
+ # .python-version
98
+
99
+ # pipenv
100
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
101
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
102
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
103
+ # install all needed dependencies.
104
+ #Pipfile.lock
105
+
106
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
107
+ __pypackages__/
108
+
109
+ # Celery stuff
110
+ celerybeat-schedule
111
+ celerybeat.pid
112
+
113
+ # SageMath parsed files
114
+ *.sage.py
115
+
116
+ # Environments
117
+ .env
118
+ .venv
119
+ env/
120
+ venv/
121
+ ENV/
122
+ env.bak/
123
+ venv.bak/
124
+
125
+ # Spyder project settings
126
+ .spyderproject
127
+ .spyproject
128
+
129
+ # Rope project settings
130
+ .ropeproject
131
+
132
+ # mkdocs documentation
133
+ /site
134
+
135
+ # mypy
136
+ .mypy_cache/
137
+ .dmypy.json
138
+ dmypy.json
139
+
140
+ # Pyre type checker
141
+ .pyre/
142
+
143
+ # pytype static type analyzer
144
+ .pytype/
145
+
146
+ # Cython debug symbols
147
+ cython_debug/
148
+
149
+ # Ruff cache
150
+ .ruff_cache
151
+
152
+ # Default pytest output
153
+ test-output.xml
@@ -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.
@@ -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,49 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: Contributors to openadr3-client-gac-compliance <https://github.com/ElaadNL/openadr3-client-gac-compliance>
3
+
4
+ SPDX-License-Identifier: Apache-2.0
5
+ -->
6
+
7
+ [![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)
8
+ [![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)
9
+ ![PYPI-DL](https://img.shields.io/pypi/dm/openadr3-client-gac-compliance?style=flat)
10
+ [![image](https://img.shields.io/pypi/v/openadr3-client-gac-compliance?label=pypi)](https://pypi.python.org/pypi/openadr3-client-gac-compliance)
11
+ ![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)
12
+ [![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)
13
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
14
+
15
+ # OpenADR3 client
16
+
17
+ 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.
18
+
19
+ Registering the plugin is done using the global ValidatorPluginRegistry class:
20
+
21
+ ```python
22
+ from openadr3_client.plugin import ValidatorPluginRegistry, ValidatorPlugin
23
+ from openadr3_client_gac_compliance.gac20.plugin import Gac20ValidatorPlugin
24
+
25
+ ValidatorPluginRegistry.register_plugin(
26
+ Gac20ValidatorPlugin().setup()
27
+ )
28
+ ```
29
+
30
+ ## License
31
+
32
+ This project is licensed under the Apache-2.0 - see LICENSE for details.
33
+
34
+ ## Licenses third-party libraries
35
+
36
+ This project includes third-party libraries, which are licensed under their own respective Open-Source licenses.
37
+ SPDX-License-Identifier headers are used to show which license is applicable. The concerning license files can be found in the LICENSES directory.
38
+
39
+ ---
40
+
41
+ ## About ElaadNL
42
+
43
+ OpenADRGUI is built by ElaadNL, with the goal of using it for both internal projects as well as those of stakeholders.
44
+
45
+ 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.
46
+
47
+ 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.
48
+
49
+ 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.
@@ -1,36 +1,60 @@
1
+ # SPDX-FileCopyrightText: Contributors to openadr3-client-gac-compliance <https://github.com/ElaadNL/openadr3-client-gac-compliance>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
1
5
  [project]
2
6
  name = "openadr3-client-gac-compliance"
3
- version = "3.0.1"
7
+ version = "3.0.3"
4
8
  description = ""
5
9
  authors = [
6
- {name = "Nick van der Burgt", email = "nick.van.der.burgt@elaad.nl"}
10
+ { name = "Nick van der Burgt", email = "nick.van.der.burgt@elaad.nl" },
11
+ { name = "Stijn van Houwelingen", email = "stijn.van.houwelingen@elaad.nl" },
7
12
  ]
8
- readme = "README.md"
9
13
  requires-python = ">=3.12, <4"
14
+ readme = "README.md"
15
+ license = "Apache-2.0"
10
16
  dependencies = [
11
17
  "pydantic (>=2.11.2,<3.0.0)",
12
18
  "openadr3-client (>=1.0.0a1,<2.0.0)",
13
19
  "pycountry (>=24.6.1,<25.0.0)",
14
20
  ]
15
21
 
22
+ [project.urls]
23
+ Homepage = "https://github.com/ElaadNL/openadr3-client"
24
+ Repository = "https://github.com/ElaadNL/openadr3-client"
25
+ "Bug Tracker" = "https://github.com/ElaadNL/openadr3-client/issues"
26
+ Changelog = "https://github.com/ElaadNL/openadr3-client/releases"
27
+
28
+
16
29
  [build-system]
17
- requires = ["poetry-core>=2.0.0,<3.0.0"]
18
- build-backend = "poetry.core.masonry.api"
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
19
32
 
20
- [tool.poetry.group.dev.dependencies]
21
- mypy = "^1.15.0"
22
- ruff = "^0.11.4"
23
- pytest = "^8.3.5"
24
- pytest-cov = "^6.1.1"
25
- taskipy = "^1.14.1"
33
+ [dependency-groups]
34
+ dev = [
35
+ "mypy>=1.15.0,<2",
36
+ "ruff>=0.11.4,<0.15",
37
+ "pytest>=8.3.5,<10",
38
+ "pytest-cov>=6.1.1,<8",
39
+ "taskipy>=1.14.1,<2",
40
+ "bandit[toml]>=1.9.2",
41
+ "reuse>=6.2.0",
42
+ "pre-commit>=4.5.1",
43
+ ]
44
+
45
+ [tool.hatch.build.targets.sdist]
46
+ include = ["openadr3_client"]
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ include = ["openadr3_client"]
26
50
 
27
51
  [[tool.mypy.overrides]]
28
52
  module = ["openadr3_client"]
29
53
  ignore_missing_imports = true
30
54
 
31
55
  [tool.taskipy.tasks]
32
- local-ci = "task check && task format && task mypy && task test"
33
- fix = "task check-fix && task format-fix && task mypy"
56
+ local-ci = "task check && task format && task mypy && task bandit && task test && task reuse"
57
+ fix = "task check-fix && task format-fix && task mypy && task bandit"
34
58
  check = "ruff check ."
35
59
  check-fix = "ruff check --fix ."
36
60
  check-ci = "ruff check --output-format=github --no-cache ."
@@ -38,7 +62,22 @@ mypy = "mypy . --check-untyped-defs"
38
62
  format = "ruff format . --diff"
39
63
  format-fix = "ruff format ."
40
64
  format-ci = "ruff format --diff --no-cache ."
41
- test = "pytest --cov"
65
+ bandit = "bandit -c pyproject.toml -r ."
66
+ # Will only add headers to files that are not already licensed.
67
+ # Cannot ignore uv.lock through REUSE.toml, see https://codeberg.org/fsfe/reuse-tool/pulls/1303
68
+ reuse-fix = """
69
+ reuse annotate \
70
+ --license Apache-2.0 \
71
+ --copyright "Contributors to openadr3-client-gac-compliance <https://github.com/ElaadNL/openadr3-client-gac-compliance>" \
72
+ --exclude-year \
73
+ --recursive \
74
+ --skip-unrecognised \
75
+ --skip-existing \
76
+ .
77
+ """
78
+ reuse = "reuse lint"
79
+ test = "pytest --cov --doctest-modules openadr3_client_gac_compliance/ tests/"
80
+
42
81
 
43
82
  [tool.ruff]
44
83
  line-length = 120
@@ -76,4 +115,7 @@ ignore = [
76
115
 
77
116
  # Ignores S101 ("Use of 'assert' detected") for the tests.
78
117
  [tool.ruff.lint.per-file-ignores]
79
- "tests/*" = ["S101", "ANN201", "SLF001", "S106", "S105", "S311", "PLR2004", "PT018"]
118
+ "tests/*" = ["S101", "ANN201", "SLF001", "S106", "S105", "S311", "PLR2004", "PT018"]
119
+
120
+ [tool.bandit]
121
+ exclude_dirs = ["tests", ".venv"]
@@ -1,31 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: openadr3-client-gac-compliance
3
- Version: 3.0.1
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,14 +0,0 @@
1
- # OpenADR3 client
2
-
3
- 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.
4
-
5
- Registering the plugin is done using the global ValidatorPluginRegistry class:
6
-
7
- ```python
8
- from openadr3_client.plugin import ValidatorPluginRegistry, ValidatorPlugin
9
- from openadr3_client_gac_compliance.gac20.plugin import Gac20ValidatorPlugin
10
-
11
- ValidatorPluginRegistry.register_plugin(
12
- Gac20ValidatorPlugin().setup()
13
- )
14
- ```
@@ -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,414 +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
- if len(payload.values) > 1:
363
- validation_errors.append(
364
- InitErrorDetails(
365
- type=PydanticCustomError(
366
- "value_error",
367
- "The event interval payload must have exactly one value per payload.",
368
- ),
369
- loc=("intervals",),
370
- input=self.intervals,
371
- ctx={},
372
- )
373
- )
374
-
375
- return validation_errors
376
-
377
-
378
- def validate_event_gac_compliant(event: Event) -> list[InitErrorDetails] | None:
379
- """
380
- Validates that events are GAC compliant.
381
-
382
- The following constraints are enforced for events:
383
-
384
- - The event must not have a priority set.
385
- - The event must have either a continuous or separated interval definition.
386
- """
387
- validation_errors: list[InitErrorDetails] = []
388
-
389
- if event.priority is not None:
390
- validation_errors.append(
391
- InitErrorDetails(
392
- type=PydanticCustomError(
393
- "value_error",
394
- "The event must not have a priority set for GAC 2.0 compliance",
395
- ),
396
- loc=("priority",),
397
- input=event.priority,
398
- ctx={},
399
- )
400
- )
401
-
402
- errors = _continuous_or_separated(event)
403
- validation_errors.extend(errors)
404
-
405
- errors = _targets_compliant(event)
406
- validation_errors.extend(errors)
407
-
408
- errors = _payload_descriptors_gac_compliant(event)
409
- validation_errors.extend(errors)
410
-
411
- errors = _event_interval_gac_compliant(event)
412
- validation_errors.extend(errors)
413
-
414
- 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.