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.
- openadr3_client_gac_compliance-3.0.3/.gitignore +153 -0
- {openadr3_client_gac_compliance-3.0.1 → openadr3_client_gac_compliance-3.0.3}/LICENSE.md +1 -1
- openadr3_client_gac_compliance-3.0.3/PKG-INFO +65 -0
- openadr3_client_gac_compliance-3.0.3/README.md +49 -0
- {openadr3_client_gac_compliance-3.0.1 → openadr3_client_gac_compliance-3.0.3}/pyproject.toml +57 -15
- openadr3_client_gac_compliance-3.0.1/PKG-INFO +0 -31
- openadr3_client_gac_compliance-3.0.1/README.md +0 -14
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/__init__.py +0 -22
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/__init__.py +0 -4
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/event_gac_compliant.py +0 -414
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/plugin.py +0 -43
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/program_gac_compliant.py +0 -100
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/ven_gac_compliant.py +0 -49
- openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac21/__init__.py +0 -1
|
@@ -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
|
+
[](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/codeql.yml)
|
|
24
|
+
[](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/ci.yml)
|
|
25
|
+

|
|
26
|
+
[](https://pypi.python.org/pypi/openadr3-client-gac-compliance)
|
|
27
|
+

|
|
28
|
+
[](https://github.com/astral-sh/ruff)
|
|
29
|
+
[](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
|
+
[](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/codeql.yml)
|
|
8
|
+
[](https://github.com/ElaadNL/openadr3-client-gac-compliance/actions/workflows/ci.yml)
|
|
9
|
+

|
|
10
|
+
[](https://pypi.python.org/pypi/openadr3-client-gac-compliance)
|
|
11
|
+

|
|
12
|
+
[](https://github.com/astral-sh/ruff)
|
|
13
|
+
[](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.
|
{openadr3_client_gac_compliance-3.0.1 → openadr3_client_gac_compliance-3.0.3}/pyproject.toml
RENAMED
|
@@ -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.
|
|
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 = ["
|
|
18
|
-
build-backend = "
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
19
32
|
|
|
20
|
-
[
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
pytest
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/event_gac_compliant.py
DELETED
|
@@ -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
|
openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/program_gac_compliant.py
DELETED
|
@@ -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
|
openadr3_client_gac_compliance-3.0.1/openadr3_client_gac_compliance/gac20/ven_gac_compliant.py
DELETED
|
@@ -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.
|