Skip to content

Commit 3193cd5

Browse files
Merge pull request #43 from DACCS-Climate/keywords-to-types
replace keywords with types and relax restrictions on keywords
2 parents b69ad6c + 3e1f652 commit 3193cd5

File tree

7 files changed

+125
-11
lines changed

7 files changed

+125
-11
lines changed

doc/node_registry.example.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
"service-wms",
4242
"service-wfs"
4343
],
44+
"types": [
45+
"data",
46+
"wps",
47+
"wms",
48+
"wfs"
49+
],
4450
"description": "GeoServer is a server that allows users to view and edit geospatial data.",
4551
"links": [
4652
{
@@ -60,6 +66,9 @@
6066
"keywords": [
6167
"service-ogcapi_processes"
6268
],
69+
"types": [
70+
"ogcapi_processes"
71+
],
6372
"description": "An OGC-API flavored Execution Management Service",
6473
"links": [
6574
{

marble_node_registry/migrations.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Migrations are used to ensure that data provided by nodes that are using an
2+
# older version of the schema are updated automatically to comply with the newest
3+
# version of the schema.
4+
#
5+
# If a backwards incompatible change is introduced in the schema, please make a
6+
# new migration function here to ensure that older data is properly updated.
7+
#
8+
# Migration functions will be applied to each node's data in the order that they
9+
# appear in the MIGRATIONS variable at the bottom of this file.
10+
#
11+
# All migration function should take a single argument which contains the node's
12+
# data and modify that data in place.
13+
14+
def convert_keywords_to_types(data: dict) -> None:
15+
"""
16+
Add service types if they don't exist.
17+
18+
Since version 1.3.0 service "types" are now required. If they don't exist
19+
then they can be derived from service "keywords" which were used in place
20+
of "types" prior to this version.
21+
"""
22+
23+
keyword2type = {
24+
"catalog": "catalog",
25+
"data": "data",
26+
"jupyterhub": "jupyterhub",
27+
"other": "other",
28+
"service-wps": "wps",
29+
"service-wms": "wms",
30+
"service-wfs": "wfs",
31+
"service-wcs": "wcs",
32+
"service-ogcapi_processes": "ogcapi_processes"
33+
}
34+
for service in data["services"]:
35+
if "types" not in service:
36+
service["types"] = []
37+
for keyword in service["keywords"]:
38+
if (type_ := keyword2type.get(keyword)):
39+
service["types"].append(type_)
40+
if not service["types"]:
41+
service["types"].append("other")
42+
43+
44+
MIGRATIONS = (
45+
convert_keywords_to_types,
46+
)

marble_node_registry/update.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import datetime
77
from copy import deepcopy
88

9+
from migrations import MIGRATIONS
10+
911
THIS_DIR = os.path.dirname(__file__)
1012
ROOT_DIR = os.path.dirname(THIS_DIR)
1113
SCHEMA_FILE = os.path.join(ROOT_DIR, "node_registry.schema.json")
@@ -89,6 +91,15 @@ def update_registry() -> None:
8991
)
9092
continue
9193

94+
try:
95+
for migration in MIGRATIONS:
96+
migration(data)
97+
except Exception as e:
98+
registry[name] = org_data
99+
registry[name]["status"] = "invalid_configuration"
100+
sys.stderr.write(f"unable to apply migrations for Node named {name}: {e}.")
101+
continue
102+
92103
try:
93104
jsonschema.validate(instance=registry, schema=schema)
94105
except jsonschema.exceptions.ValidationError as e:

node_registry.schema.json

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://raw.githubusercontent.com/DACCS-Climate/Marble-node-registry/refs/tags/1.3.0/node_registry.schema.json",
34
"patternProperties": {
45
"^[a-zA-Z0-9]+$": {
56
"type": "object",
@@ -150,6 +151,7 @@
150151
"type": "object",
151152
"required": [
152153
"name",
154+
"types",
153155
"keywords",
154156
"description",
155157
"links"
@@ -159,12 +161,36 @@
159161
"type": "string",
160162
"minLength": 1
161163
},
162-
"keywords": {
164+
"types": {
163165
"type": "array",
164166
"minItems": 1,
165167
"items": {
166168
"type": "string",
167-
"pattern": "^catalog|data|jupyterhub|other|service-(wps|wms|wfs|wcs|ogcapi_processes)$"
169+
"enum": [
170+
"auth",
171+
"management",
172+
"catalog",
173+
"stac",
174+
"data",
175+
"jupyterhub",
176+
"other",
177+
"wps",
178+
"wms",
179+
"wfs",
180+
"wcs",
181+
"ogcapi_processes",
182+
"ogcapi_dggs",
183+
"ogcapi_coverages",
184+
"ogcapi_features",
185+
"ogcapi_records"
186+
]
187+
}
188+
},
189+
"keywords": {
190+
"type": "array",
191+
"items": {
192+
"type": "string",
193+
"minLength": 1
168194
}
169195
},
170196
"description": {

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
addopts = [
33
"--import-mode=importlib",
44
]
5-
pythonpath = "."
5+
pythonpath = "./marble_node_registry"

tests/test_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def registry_content_with_services(registry_content):
3535
{
3636
"name": "test-service",
3737
"keywords": ["other"],
38+
"types": ["other"],
3839
"description": "test service",
3940
"version": "1.2.3",
4041
"links": [

tests/test_update.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
import jsonschema
66
import pytest
77

8-
from marble_node_registry import update
8+
import update # type: ignore
99

1010
GOOD_SERVICES = {
1111
"services": [
1212
{
1313
"name": "geoserver",
14+
"types": ["data", "wps", "wms", "wfs"],
1415
"keywords": ["data", "service-wps", "service-wms", "service-wfs"],
1516
"description": "GeoServer is a server that allows users to view and edit geospatial data.",
1617
"links": [
@@ -20,7 +21,8 @@
2021
},
2122
{
2223
"name": "weaver",
23-
"keywords": ["service-ogcapi_processes"],
24+
"types": ["ogcapi_processes"],
25+
"keywords": ["service-ogcapi_processes", "some-other-keyword"],
2426
"description": "An OGC-API flavored Execution Management Service",
2527
"links": [
2628
{"rel": "service", "type": "application/json", "href": "https://daccs-uoft.example.com/weaver/"},
@@ -265,7 +267,7 @@ def test_last_updated_updated(self, example_node_name, example_registry_content,
265267
example_node_name
266268
].get("last_updated")
267269

268-
def test_final_registry_valid(self, updated_registry, node_registry_schema):
270+
def test_final_registry_validity(self, updated_registry, node_registry_schema):
269271
jsonschema.validate(instance=updated_registry.call_args.args[0], schema=node_registry_schema)
270272

271273

@@ -300,7 +302,7 @@ def test_last_updated_no_change(self, example_node_name, example_registry_conten
300302
example_node_name
301303
].get("last_updated")
302304

303-
def test_final_registry_valid(self, updated_registry, node_registry_schema):
305+
def test_final_registry_validity(self, updated_registry, node_registry_schema):
304306
jsonschema.validate(instance=updated_registry.call_args.args[0], schema=node_registry_schema)
305307

306308

@@ -350,11 +352,30 @@ class TestOnlineNodeUpdateWithInvalidServices(InvalidResponseTests, NonInitialTe
350352
services = {"services": [{"bad_key": "some_value"}]}
351353

352354

353-
class TestOnlineNodeUpdateWithInvalidServiceKeywords(InvalidResponseTests, NonInitialTests):
354-
"""Test when updates have previously been run and the reported services keywords are not valid"""
355+
class TestOnlineNodeUpdateWithInvalidServiceTypes(InvalidResponseTests, NonInitialTests):
356+
"""Test when updates have previously been run and the reported services types are not valid"""
355357

356358
services = deepcopy(GOOD_SERVICES)
357359

358360
@pytest.fixture(scope="class", autouse=True)
359-
def bad_keywords(self):
360-
self.services["services"][0]["keywords"] = ["something-bad"]
361+
def bad_types(self):
362+
self.services["services"][0]["types"] = ["something-bad"]
363+
364+
365+
class TestOnlineNodeUpdateWithNoTypes(ValidResponseTests, NonInitialTests):
366+
"""
367+
Test when updates have previously been run and there are no services types
368+
369+
This ensures that service types are updated as expected from the provided keywords
370+
"""
371+
372+
services = deepcopy(GOOD_SERVICES)
373+
374+
@pytest.fixture(scope="class", autouse=True)
375+
def no_types(self):
376+
for service in self.services["services"]:
377+
service.pop("types")
378+
379+
def test_services_updated(self, example_node_name, updated_registry):
380+
"""Test that the services values are updated"""
381+
assert updated_registry.call_args.args[0][example_node_name]["services"] == GOOD_SERVICES["services"]

0 commit comments

Comments
 (0)