pywmdr 0.2.dev1__tar.gz → 0.2.dev3__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.
- {pywmdr-0.2.dev1/pywmdr.egg-info → pywmdr-0.2.dev3}/PKG-INFO +1 -1
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pyproject.toml +1 -1
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/bundle.py +19 -0
- pywmdr-0.2.dev3/pywmdr/codelists.py +122 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/pygeoapi_plugin.py +1 -1
- pywmdr-0.2.dev3/pywmdr/resources/0-20008-0-THE.json +176 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/resources/ets-report.json +1 -1
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/wmdr2/ets.py +70 -1
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3/pywmdr.egg-info}/PKG-INFO +1 -1
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr.egg-info/SOURCES.txt +3 -1
- pywmdr-0.2.dev3/tests/test_codelists.py +45 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/tests/test_wmdr_ets.py +7 -5
- pywmdr-0.2.dev1/pywmdr/resources/20250504_0-20008-0-NRB.json +0 -94
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/LICENSE.md +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/README.md +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/__init__.py +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/cli_options.py +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/errors.py +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/ets.py +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/util.py +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr/wmdr2/__init__.py +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr.egg-info/dependency_links.txt +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr.egg-info/entry_points.txt +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr.egg-info/requires.txt +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/pywmdr.egg-info/top_level.txt +0 -0
- {pywmdr-0.2.dev1 → pywmdr-0.2.dev3}/setup.cfg +0 -0
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
#
|
|
20
20
|
###############################################################################
|
|
21
21
|
|
|
22
|
+
import csv
|
|
23
|
+
import io
|
|
22
24
|
import logging
|
|
23
25
|
from pathlib import Path
|
|
24
26
|
import shutil
|
|
@@ -38,6 +40,8 @@ TEMPDIR2 = Path(tempfile.TemporaryDirectory().name)
|
|
|
38
40
|
|
|
39
41
|
WMDR2_FILES = get_userdir() / 'wmdr2'
|
|
40
42
|
WMDR2_FILES_TEMP = TEMPDIR2 / 'wmdr2'
|
|
43
|
+
WMDS_DIR = get_userdir() / 'wmds'
|
|
44
|
+
WMDS_DIR_TEMP = TEMPDIR2 / 'wmds'
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
@click.group()
|
|
@@ -61,6 +65,21 @@ def sync(ctx, verbosity):
|
|
|
61
65
|
with json_schema.open('wb') as fh:
|
|
62
66
|
fh.write(urlopen_(f'{WMDR2_SCHEMA}').read())
|
|
63
67
|
|
|
68
|
+
LOGGER.debug('Caching codelists')
|
|
69
|
+
LOGGER.debug(f'Downloading WMDS codelists to {WMDS_DIR_TEMP}')
|
|
70
|
+
WMDS_DIR_TEMP.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
WMDS_CODELISTS_LIST = 'https://codes.wmo.int/wmdr?_format=csv&_view=with_metadata&status=valid' # noqa
|
|
72
|
+
codelists = urlopen_(WMDS_CODELISTS_LIST).read().decode('utf-8')
|
|
73
|
+
codelists_fh = io.StringIO(codelists.strip())
|
|
74
|
+
reader = csv.reader(codelists_fh)
|
|
75
|
+
for row in reader:
|
|
76
|
+
codelist_url = f'https://codes.wmo.int/wmdr/{row[1]}?_format=csv&_view=with_metadata&status=valid' # noqa
|
|
77
|
+
LOGGER.debug(f'Downloading {codelist_url}')
|
|
78
|
+
codelist_file = WMDS_DIR_TEMP / f'{row[1]}.csv'
|
|
79
|
+
LOGGER.debug(f'Writing codelist {codelist_file}')
|
|
80
|
+
with codelist_file.open('wb') as fh:
|
|
81
|
+
fh.write(urlopen_(codelist_url).read())
|
|
82
|
+
|
|
64
83
|
LOGGER.debug(f'Removing {USERDIR}')
|
|
65
84
|
if USERDIR.exists():
|
|
66
85
|
shutil.rmtree(USERDIR)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
##############################################################################
|
|
2
|
+
#
|
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
# or more contributor license agreements. See the NOTICE file
|
|
5
|
+
# distributed with this work for additional information
|
|
6
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
# to you under the Apache License, Version 2.0 (the
|
|
8
|
+
# "License"); you may not use this file except in compliance
|
|
9
|
+
# with the License. You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing,
|
|
14
|
+
# software distributed under the License is distributed on an
|
|
15
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
# KIND, either express or implied. See the License for the
|
|
17
|
+
# specific language governing permissions and limitations
|
|
18
|
+
# under the License.
|
|
19
|
+
#
|
|
20
|
+
###############################################################################
|
|
21
|
+
|
|
22
|
+
import csv
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
from pywmdr.util import get_userdir
|
|
26
|
+
|
|
27
|
+
LOGGER = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
CODELISTS_MAP = {
|
|
30
|
+
'facilityType': {
|
|
31
|
+
'codelists': ['FacilityType.csv']
|
|
32
|
+
},
|
|
33
|
+
'wmoRegion': {
|
|
34
|
+
'codelists': ['WMORegion.csv']
|
|
35
|
+
},
|
|
36
|
+
'territory': {
|
|
37
|
+
'codelists': ['TerritoryName.csv']
|
|
38
|
+
},
|
|
39
|
+
'observedGeometry': {
|
|
40
|
+
'codelists': ['Geometry.csv']
|
|
41
|
+
},
|
|
42
|
+
'observedProperty': {
|
|
43
|
+
'codelists': [
|
|
44
|
+
'ObservedVariableAtmosphere.csv',
|
|
45
|
+
'ObservedVariableEarth.csv',
|
|
46
|
+
'ObservedVariableOcean.csv',
|
|
47
|
+
'ObservedVariableOuterSpace.csv',
|
|
48
|
+
'ObservedVariableTerrestrial.csv'
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
'programAffiliation': {
|
|
52
|
+
'codelists': ['ProgramAffiliation.csv']
|
|
53
|
+
},
|
|
54
|
+
'reportingStatus': {
|
|
55
|
+
'codelists': ['ReportingStatus.csv']
|
|
56
|
+
},
|
|
57
|
+
'observingMethod': {
|
|
58
|
+
'codelists': [
|
|
59
|
+
'ObservingMethodAtmosphere.csv',
|
|
60
|
+
'ObservingMethodTerrestrial.csv',
|
|
61
|
+
'ObservingMethodOcean.csv'
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
'operatingStatus': {
|
|
65
|
+
'codelists': ['InstrumentOperatingStatus.csv']
|
|
66
|
+
},
|
|
67
|
+
'sourceOfObservation': {
|
|
68
|
+
'codelists': ['SourceOfObservation.csv']
|
|
69
|
+
},
|
|
70
|
+
'referenceSurface': {
|
|
71
|
+
'codelists': ['ReferenceSurfaceType.csv']
|
|
72
|
+
},
|
|
73
|
+
'unit': {
|
|
74
|
+
'codelists': ['unit.csv']
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class WMDSCodelists:
|
|
80
|
+
def __init__(self):
|
|
81
|
+
"""initializer"""
|
|
82
|
+
|
|
83
|
+
self.codelists = {}
|
|
84
|
+
for key, value in CODELISTS_MAP.items():
|
|
85
|
+
self.codelists[key] = []
|
|
86
|
+
for codelist in value['codelists']:
|
|
87
|
+
filename = get_userdir() / 'wmds' / codelist
|
|
88
|
+
with filename.open() as fh:
|
|
89
|
+
reader = csv.DictReader(fh)
|
|
90
|
+
for row in reader:
|
|
91
|
+
self.codelists[key].append(row['@notation'])
|
|
92
|
+
|
|
93
|
+
def is_valid(self, pname: str, pvalue: dict | None) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
Helper function to determine whether a concept is valid
|
|
96
|
+
(WMDS codelist or null)
|
|
97
|
+
|
|
98
|
+
:param pname: name of property/object
|
|
99
|
+
:param value: value of property/object
|
|
100
|
+
|
|
101
|
+
:returns: `bool` of whether a concept is valid
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
if pname not in self.codelists:
|
|
105
|
+
msg = f'{pname} not found in codelists'
|
|
106
|
+
LOGGER.error(msg)
|
|
107
|
+
raise ValueError(msg)
|
|
108
|
+
|
|
109
|
+
if pvalue is None:
|
|
110
|
+
LOGGER.debug('Value is null')
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
LOGGER.debug(f'Property name: {pname}')
|
|
114
|
+
LOGGER.debug(f'Property value: {pvalue}')
|
|
115
|
+
LOGGER.debug(f'Codelist: {self.codelists[pname]}')
|
|
116
|
+
if pvalue['id'] in self.codelists[pname]:
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def __repr__(self):
|
|
122
|
+
return '<WMDSCodelists>'
|
|
@@ -60,7 +60,7 @@ LOGGER = logging.getLogger(__name__)
|
|
|
60
60
|
with (THISDIR / 'resources' / 'ets-report.json').open() as fh:
|
|
61
61
|
ETS_REPORT_SCHEMA = json.load(fh)
|
|
62
62
|
|
|
63
|
-
with (THISDIR / 'resources' / '
|
|
63
|
+
with (THISDIR / 'resources' / '0-20008-0-THE.json').open() as fh:
|
|
64
64
|
EXAMPLE_WMDR2 = json.load(fh)
|
|
65
65
|
|
|
66
66
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "Feature",
|
|
3
|
+
"id": "0-20008-0-THE",
|
|
4
|
+
"geometry": {
|
|
5
|
+
"type": "Point",
|
|
6
|
+
"coordinates": [
|
|
7
|
+
22.956,
|
|
8
|
+
40.634,
|
|
9
|
+
60
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"time": {
|
|
13
|
+
"interval": [
|
|
14
|
+
"1982-03-13",
|
|
15
|
+
".."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"conformsTo": [
|
|
19
|
+
"http://wigos.wmo.int/spec/wmdr/2/conf/core"
|
|
20
|
+
],
|
|
21
|
+
"properties": {
|
|
22
|
+
"type": "facility",
|
|
23
|
+
"facilityType": {
|
|
24
|
+
"id": "landFixed"
|
|
25
|
+
},
|
|
26
|
+
"title": "Thessaloniki",
|
|
27
|
+
"created": "2024-12-11T00:00:00Z",
|
|
28
|
+
"updated": "2024-12-11T00:00:00Z",
|
|
29
|
+
"description": "The Brewer spectrophotometers and all radiation instruments are located on the roof of the 4-storey building of the Physics Dept., of the Aristotle University of Thessaloniki. The Campus is located in the city center about 500 m east of the seashore. The highest elevation of about 1100 m is at a distance of about 15 km towards the east. Thessaloniki is the second largest city of Greece with about 1000000 inhabitants in the greater metropolitan area.",
|
|
30
|
+
"contacts": [
|
|
31
|
+
{
|
|
32
|
+
"identifier": "contact:aristotle-university-of-thessaloniki--ed7b7e4a",
|
|
33
|
+
"organization": "Aristotle University of Thessaloniki",
|
|
34
|
+
"roles": [
|
|
35
|
+
"owner"
|
|
36
|
+
],
|
|
37
|
+
"links": [
|
|
38
|
+
{
|
|
39
|
+
"rel": "about",
|
|
40
|
+
"href": "https://lapweb.physics.auth.gr",
|
|
41
|
+
"type": "text/html"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"identifier": "contact:clima@hnms.gr",
|
|
47
|
+
"name": "Mrs M. Michelaraki",
|
|
48
|
+
"organization": "Hellenic National Meteorological Service",
|
|
49
|
+
"roles": [
|
|
50
|
+
"pointOfContact"
|
|
51
|
+
],
|
|
52
|
+
"links": [
|
|
53
|
+
{
|
|
54
|
+
"rel": "about",
|
|
55
|
+
"href": "https://www.emy.gr",
|
|
56
|
+
"type": "text/html"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"identifier": "contact:abais@auth.gr",
|
|
62
|
+
"name": "Alkiviadis Bais",
|
|
63
|
+
"organization": "Aristotle University of Thessaloniki",
|
|
64
|
+
"roles": [
|
|
65
|
+
"principalInvestigator"
|
|
66
|
+
],
|
|
67
|
+
"links": [
|
|
68
|
+
{
|
|
69
|
+
"rel": "about",
|
|
70
|
+
"href": "https://lapweb.physics.auth.gr",
|
|
71
|
+
"type": "text/html"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"identifier": "contact:balis@auth.gr",
|
|
77
|
+
"name": "Dimitris Balis",
|
|
78
|
+
"organization": "Aristotle University of Thessaloniki",
|
|
79
|
+
"roles": [
|
|
80
|
+
"principalInvestigator"
|
|
81
|
+
],
|
|
82
|
+
"links": [
|
|
83
|
+
{
|
|
84
|
+
"rel": "about",
|
|
85
|
+
"href": "https://lapweb.physics.auth.gr",
|
|
86
|
+
"type": "text/html"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"wmoRegion": {
|
|
92
|
+
"id": "europe"
|
|
93
|
+
},
|
|
94
|
+
"territories": [
|
|
95
|
+
{
|
|
96
|
+
"dates": [
|
|
97
|
+
"2011-02-16",
|
|
98
|
+
".."
|
|
99
|
+
],
|
|
100
|
+
"territory": {"id": "GRC"}
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"observations": [
|
|
104
|
+
{
|
|
105
|
+
"observedProperty": {
|
|
106
|
+
"id": "12164"
|
|
107
|
+
},
|
|
108
|
+
"programAffiliations": [
|
|
109
|
+
{
|
|
110
|
+
"dates": [
|
|
111
|
+
"1982-03-13",
|
|
112
|
+
".."
|
|
113
|
+
],
|
|
114
|
+
"programAffiliation": {
|
|
115
|
+
"id": "GAWregional"
|
|
116
|
+
},
|
|
117
|
+
"reportingStatus": {
|
|
118
|
+
"id": "operational"
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"dates": [
|
|
123
|
+
"2008-01-01",
|
|
124
|
+
"2024-12-11"
|
|
125
|
+
],
|
|
126
|
+
"programAffiliation": {
|
|
127
|
+
"id": "GALION"
|
|
128
|
+
},
|
|
129
|
+
"reportingStatus": {
|
|
130
|
+
"id": "operational"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"dates": [
|
|
135
|
+
"2003-06-01",
|
|
136
|
+
"2024-12-11"
|
|
137
|
+
],
|
|
138
|
+
"programAffiliation": {
|
|
139
|
+
"id": "AERONET"
|
|
140
|
+
},
|
|
141
|
+
"reportingStatus": {
|
|
142
|
+
"id": "operational"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"configurations": [
|
|
147
|
+
{
|
|
148
|
+
"id": "configuration-456",
|
|
149
|
+
"time": {
|
|
150
|
+
"interval": [
|
|
151
|
+
"1982-03-13",
|
|
152
|
+
".."
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
"observingMethod": {
|
|
156
|
+
"id": "78"
|
|
157
|
+
},
|
|
158
|
+
"sourceOfObservation": {
|
|
159
|
+
"id": "automaticReading"
|
|
160
|
+
},
|
|
161
|
+
"instrument": {
|
|
162
|
+
"id": "instrument-789"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
"links": [
|
|
170
|
+
{
|
|
171
|
+
"href": "https://lapweb.physics.auth.gr/en/infrastructure/#Infrastructure",
|
|
172
|
+
"rel": "about",
|
|
173
|
+
"type": "text/html"
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema: https://json-schema.org/draft/2020-12/schema",
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/wmo-im/pywmdr/refs/heads/main/pywmdr/resources/ets-report.json",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"title": "WMDR2 Executable Test Suite report",
|
|
@@ -27,6 +27,7 @@ from jsonschema import FormatChecker
|
|
|
27
27
|
from jsonschema.validators import Draft202012Validator
|
|
28
28
|
|
|
29
29
|
from pywmdr.bundle import WMDR2_FILES
|
|
30
|
+
from pywmdr.codelists import WMDSCodelists
|
|
30
31
|
from pywmdr.errors import TestSuiteError
|
|
31
32
|
from pywmdr.util import get_current_datetime_rfc3339, get_package_version
|
|
32
33
|
|
|
@@ -42,7 +43,7 @@ def gen_test_id(test_id: str) -> str:
|
|
|
42
43
|
:returns: test identifier as URI
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
return f'http://
|
|
46
|
+
return f'http://wigos.wmo.int/spec/wmdr/2/conf/core/{test_id}'
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class WMDR2TestSuite:
|
|
@@ -57,6 +58,7 @@ class WMDR2TestSuite:
|
|
|
57
58
|
|
|
58
59
|
self.version = get_package_version()
|
|
59
60
|
self.errors = []
|
|
61
|
+
self.codelists = WMDSCodelists()
|
|
60
62
|
self.record = record
|
|
61
63
|
|
|
62
64
|
def run_tests(self):
|
|
@@ -144,6 +146,73 @@ class WMDR2TestSuite:
|
|
|
144
146
|
|
|
145
147
|
return status
|
|
146
148
|
|
|
149
|
+
def test_requirement_codelisted_values(self):
|
|
150
|
+
"""
|
|
151
|
+
Test property values against required codelists
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
validation_errors = []
|
|
155
|
+
|
|
156
|
+
status = {
|
|
157
|
+
'id': gen_test_id('codelisted_values'),
|
|
158
|
+
'code': 'PASSED'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
codelisted_values = []
|
|
162
|
+
|
|
163
|
+
LOGGER.debug('Validating against codelisted values')
|
|
164
|
+
|
|
165
|
+
codelisted_values.append(
|
|
166
|
+
('facilityType', [self.record['properties']['facilityType']]))
|
|
167
|
+
|
|
168
|
+
codelisted_values.append(
|
|
169
|
+
('wmoRegion', [self.record['properties']['wmoRegion']]))
|
|
170
|
+
|
|
171
|
+
for territory in self.record['properties'].get('territories', []):
|
|
172
|
+
codelisted_values.append(('territory', [territory['territory']]))
|
|
173
|
+
|
|
174
|
+
for observation in self.record['properties']['observations']:
|
|
175
|
+
codelisted_values.append(
|
|
176
|
+
('observedGeometry', [observation.get('observedGeometry')]))
|
|
177
|
+
codelisted_values.append(
|
|
178
|
+
('observedProperty', [observation['observedProperty']]))
|
|
179
|
+
|
|
180
|
+
for program_affiliation in observation.get('programAffiliations'):
|
|
181
|
+
codelisted_values.append(
|
|
182
|
+
('programAffiliation', [program_affiliation['programAffiliation']])) # noqa
|
|
183
|
+
codelisted_values.append(
|
|
184
|
+
('reportingStatus', [program_affiliation.get('reportingStatus')])) # noqa
|
|
185
|
+
|
|
186
|
+
for configuration in observation['configurations']:
|
|
187
|
+
codelisted_values.append(
|
|
188
|
+
('observingMethod', [configuration.get('observingMethod')])) # noqa
|
|
189
|
+
codelisted_values.append(
|
|
190
|
+
('operatingStatus', [configuration.get('operatingStatus')])) # noqa
|
|
191
|
+
codelisted_values.append(
|
|
192
|
+
('sourceOfObservation', [configuration.get('sourceOfObservation')])) # noqa
|
|
193
|
+
|
|
194
|
+
vertical_distance = configuration.get('verticalDistance')
|
|
195
|
+
|
|
196
|
+
if vertical_distance is not None:
|
|
197
|
+
codelisted_values.append(
|
|
198
|
+
('unit', [vertical_distance['unit']]))
|
|
199
|
+
codelisted_values.append(
|
|
200
|
+
('referenceSurface', [vertical_distance['referenceSurface']])) # noqa
|
|
201
|
+
|
|
202
|
+
for codelisted_value in codelisted_values:
|
|
203
|
+
LOGGER.debug(f'Testing {codelisted_value[0]} against {codelisted_value[1]}') # noqa
|
|
204
|
+
for codelisted_value2 in codelisted_value[1]:
|
|
205
|
+
if not self.codelists.is_valid(codelisted_value[0], codelisted_value2): # noqa
|
|
206
|
+
msg = f'Invalid codelist value for {codelisted_value[0]}'
|
|
207
|
+
validation_errors.append(msg)
|
|
208
|
+
|
|
209
|
+
if validation_errors:
|
|
210
|
+
status['code'] = 'FAILED'
|
|
211
|
+
status['message'] = f'{len(validation_errors)} error(s)'
|
|
212
|
+
status['errors'] = validation_errors
|
|
213
|
+
|
|
214
|
+
return status
|
|
215
|
+
|
|
147
216
|
def raise_for_status(self):
|
|
148
217
|
"""
|
|
149
218
|
Raise error if one or more failures were found during validation.
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
pywmdr/__init__.py
|
|
5
5
|
pywmdr/bundle.py
|
|
6
6
|
pywmdr/cli_options.py
|
|
7
|
+
pywmdr/codelists.py
|
|
7
8
|
pywmdr/errors.py
|
|
8
9
|
pywmdr/ets.py
|
|
9
10
|
pywmdr/pygeoapi_plugin.py
|
|
@@ -14,8 +15,9 @@ pywmdr.egg-info/dependency_links.txt
|
|
|
14
15
|
pywmdr.egg-info/entry_points.txt
|
|
15
16
|
pywmdr.egg-info/requires.txt
|
|
16
17
|
pywmdr.egg-info/top_level.txt
|
|
17
|
-
pywmdr/resources/
|
|
18
|
+
pywmdr/resources/0-20008-0-THE.json
|
|
18
19
|
pywmdr/resources/ets-report.json
|
|
19
20
|
pywmdr/wmdr2/__init__.py
|
|
20
21
|
pywmdr/wmdr2/ets.py
|
|
22
|
+
tests/test_codelists.py
|
|
21
23
|
tests/test_wmdr_ets.py
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
##############################################################################
|
|
2
|
+
#
|
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one
|
|
4
|
+
# or more contributor license agreements. See the NOTICE file
|
|
5
|
+
# distributed with this work for additional information
|
|
6
|
+
# regarding copyright ownership. The ASF licenses this file
|
|
7
|
+
# to you under the Apache License, Version 2.0 (the
|
|
8
|
+
# "License"); you may not use this file except in compliance
|
|
9
|
+
# with the License. You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing,
|
|
14
|
+
# software distributed under the License is distributed on an
|
|
15
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
16
|
+
# KIND, either express or implied. See the License for the
|
|
17
|
+
# specific language governing permissions and limitations
|
|
18
|
+
# under the License.
|
|
19
|
+
#
|
|
20
|
+
###############################################################################
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
from pywmdr.codelists import WMDSCodelists
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("pname, pvalue, expected, raise_", [
|
|
28
|
+
('unit', {'id': 'metres'}, False, False),
|
|
29
|
+
('unit', None, True, False),
|
|
30
|
+
('iunit', {'id': 'metres'}, False, True),
|
|
31
|
+
('programAffiliation', {'id': 'GBON'}, True, False),
|
|
32
|
+
('programAffiliation', {'id': '404'}, False, False)
|
|
33
|
+
])
|
|
34
|
+
def test_codelists_is_valid(pname, pvalue, expected, raise_):
|
|
35
|
+
"""Simple tests for testing codelist value validity"""
|
|
36
|
+
|
|
37
|
+
codelists = WMDSCodelists()
|
|
38
|
+
|
|
39
|
+
if raise_:
|
|
40
|
+
with pytest.raises(ValueError):
|
|
41
|
+
assert codelists.is_valid(pname, pvalue)
|
|
42
|
+
elif expected:
|
|
43
|
+
assert codelists.is_valid(pname, pvalue)
|
|
44
|
+
else:
|
|
45
|
+
assert not codelists.is_valid(pname, pvalue)
|
|
@@ -28,11 +28,13 @@ from pywmdr.wmdr2.ets import WMDR2TestSuite
|
|
|
28
28
|
from .util import get_test_file_path
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
@pytest.mark.parametrize("filename,
|
|
32
|
-
|
|
31
|
+
@pytest.mark.parametrize("filename, passed, failed, skipped, warnings_", [
|
|
32
|
+
('wmdr2-passing.json', 2, 0, 0, 0),
|
|
33
|
+
('wmdr2-failing-invalid.json', 1, 1, 0, 0),
|
|
34
|
+
('wmdr2-failing-codelisted-value.json', 1, 1, 0, 0)
|
|
33
35
|
])
|
|
34
|
-
def test_ets(filename,
|
|
35
|
-
"""Simple tests for
|
|
36
|
+
def test_ets(filename, passed, failed, skipped, warnings_):
|
|
37
|
+
"""Simple tests for ETS validation"""
|
|
36
38
|
|
|
37
39
|
with get_test_file_path(filename).open() as fh:
|
|
38
40
|
data = json.load(fh)
|
|
@@ -45,7 +47,7 @@ def test_ets(filename, failed, passed, skipped, warnings_):
|
|
|
45
47
|
|
|
46
48
|
codes = [r['code'] for r in results['tests']]
|
|
47
49
|
|
|
48
|
-
assert codes.count('FAILED') == failed
|
|
49
50
|
assert codes.count('PASSED') == passed
|
|
51
|
+
assert codes.count('FAILED') == failed
|
|
50
52
|
assert codes.count('SKIPPED') == skipped
|
|
51
53
|
assert codes.count('WARNINGS') == warnings_
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "urn:wmo:md:ke-meteo:0-20008-0-NRB",
|
|
3
|
-
"type": "Feature",
|
|
4
|
-
"conformsTo": [
|
|
5
|
-
"http://wis.wmo.int/spec/wmdr/2/conf/core"
|
|
6
|
-
],
|
|
7
|
-
"geometry": {
|
|
8
|
-
"type": "Point",
|
|
9
|
-
"coordinates": [
|
|
10
|
-
36.75919,
|
|
11
|
-
-1.30169,
|
|
12
|
-
1795.0
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
"properties": {
|
|
16
|
-
"created": "1996-01-01T00:00:00Z",
|
|
17
|
-
"wmo:dataPolicy": "core",
|
|
18
|
-
"contacts": [
|
|
19
|
-
{
|
|
20
|
-
"name": "World Meteorological Organization WMO and Federal Office for Meteorology and Climatology MeteoSwiss",
|
|
21
|
-
"organization": "World Meteorological Organization WMO and Federal Office for Meteorology and Climatology MeteoSwiss",
|
|
22
|
-
"emails": [
|
|
23
|
-
{
|
|
24
|
-
"value": "oscar@wmo.int"
|
|
25
|
-
}
|
|
26
|
-
],
|
|
27
|
-
"addresses": [
|
|
28
|
-
{
|
|
29
|
-
"deliveryPoint": [
|
|
30
|
-
"7bis, avenue de la Paix"
|
|
31
|
-
],
|
|
32
|
-
"city": "Geneva",
|
|
33
|
-
"postalCode": "CH-1211",
|
|
34
|
-
"country": "Switzerland"
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"contactInstructions": "email",
|
|
38
|
-
"links": [
|
|
39
|
-
{
|
|
40
|
-
"rel": "canonical",
|
|
41
|
-
"type": "text/html",
|
|
42
|
-
"href": "https://oscar.wmo.int/surface"
|
|
43
|
-
}
|
|
44
|
-
],
|
|
45
|
-
"roles": [
|
|
46
|
-
"host"
|
|
47
|
-
]
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"name": "Waweru,Amos,Mr",
|
|
51
|
-
"organization": "Kenyan Meteorological Department",
|
|
52
|
-
"phones": [
|
|
53
|
-
{
|
|
54
|
-
"value": "+254723521586"
|
|
55
|
-
}
|
|
56
|
-
],
|
|
57
|
-
"emails": [
|
|
58
|
-
{
|
|
59
|
-
"value": "waweru_k@yahoo.com"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"value": "amoskamau1969@gmail.com"
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
"addresses": [
|
|
66
|
-
{
|
|
67
|
-
"deliveryPoint": [
|
|
68
|
-
"Kenya Meteorological Department 30259\nNairobi \nKenya"
|
|
69
|
-
],
|
|
70
|
-
"city": "Nairobi",
|
|
71
|
-
"administrativeArea": "NB",
|
|
72
|
-
"postalCode": "00100",
|
|
73
|
-
"country": "Kenya"
|
|
74
|
-
}
|
|
75
|
-
],
|
|
76
|
-
"contactInstructions": "africa",
|
|
77
|
-
"roles": [
|
|
78
|
-
"processor",
|
|
79
|
-
"producer"
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
]
|
|
83
|
-
},
|
|
84
|
-
"links": [
|
|
85
|
-
{
|
|
86
|
-
"rel": "license",
|
|
87
|
-
"href": "https://creativecommons.org/licenses/by/4.0/"
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
"rel": "stations",
|
|
91
|
-
"href": "http://www.meteo.go.ke/obsv/ozone.html"
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|