Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.

Commit aa0f1f2

Browse files
authored
Merge pull request #65 from statisticsnorway/cache-metadata-all
cache: add thread-safe cache with 32 versions max
2 parents 03fe4a3 + 84d607d commit aa0f1f2

File tree

5 files changed

+50
-34
lines changed

5 files changed

+50
-34
lines changed

metadata_service/adapter/datastore.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from functools import lru_cache
23

34
from metadata_service.config import environment
45
from metadata_service.domain.version import Version
@@ -21,18 +22,30 @@ def get_datastore_versions() -> dict:
2122
return json.load(f)
2223

2324

24-
def get_metadata_all(version: Version) -> str:
25-
if version.is_draft():
26-
file_version = "DRAFT"
27-
else:
28-
file_version = version.to_3_underscored()
25+
def _get_draft_metadata_all():
26+
metadata_all_file_path = (
27+
f"{DATASTORE_ROOT_DIR}/datastore/metadata_all__DRAFT.json"
28+
)
29+
with open(metadata_all_file_path, "r", encoding="utf-8") as f:
30+
return json.load(f)
31+
2932

33+
@lru_cache(maxsize=32)
34+
def _get_versioned_metadata_all(version: Version):
35+
file_version = version.to_3_underscored()
3036
metadata_all_file_path = (
3137
f"{DATASTORE_ROOT_DIR}/datastore/metadata_all__{file_version}.json"
3238
)
39+
with open(metadata_all_file_path, "r", encoding="utf-8") as f:
40+
return json.load(f)
41+
42+
43+
def get_metadata_all(version: Version) -> str:
3344
try:
34-
with open(metadata_all_file_path, "r", encoding="utf-8") as f:
35-
return json.load(f)
45+
if version.is_draft():
46+
return _get_draft_metadata_all()
47+
else:
48+
return _get_versioned_metadata_all(version)
3649
except FileNotFoundError as e:
3750
raise DataNotFoundException(
3851
f"metadata_all for version {version} not found"

metadata_service/api/metadata_api.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from metadata_service.api.request_models import NameParam, MetadataQuery
66
from metadata_service.domain import metadata
7-
from metadata_service.domain.version import Version
7+
from metadata_service.domain.version import get_version_from_string
88

99
logger = logging.getLogger()
1010
metadata_api = Blueprint("metadata_api", __name__)
@@ -59,7 +59,7 @@ def get_data_structures():
5959
response = jsonify(
6060
metadata.find_data_structures(
6161
validated_query.names,
62-
Version(validated_query.version),
62+
get_version_from_string(validated_query.version),
6363
validated_query.include_attributes,
6464
validated_query.skip_code_lists,
6565
)
@@ -84,7 +84,8 @@ def get_all_metadata():
8484

8585
response = jsonify(
8686
metadata.find_all_metadata(
87-
Version(validated_query.version), validated_query.skip_code_lists
87+
get_version_from_string(validated_query.version),
88+
validated_query.skip_code_lists,
8889
)
8990
)
9091
response.headers.set("content-language", "no")

metadata_service/domain/version.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
11
from dataclasses import dataclass
22

33

4-
@dataclass
4+
@dataclass(frozen=True)
55
class Version:
66
major: str
77
minor: str
88
patch: str
99
draft: str
1010

11-
def __init__(self, version: str):
12-
split = version.split(".")
13-
self.major = split[0]
14-
self.minor = split[1]
15-
self.patch = split[2]
16-
self.draft = split[3]
17-
1811
def to_3_underscored(self):
1912
return "_".join([self.major, self.minor, self.patch])
2013

@@ -26,3 +19,8 @@ def is_draft(self):
2619

2720
def __str__(self):
2821
return ".".join([self.major, self.minor, self.patch, self.draft])
22+
23+
24+
def get_version_from_string(version: str):
25+
split = version.split(".")
26+
return Version(split[0], split[1], split[2], split[3])

tests/unit/api/test_metadata_api.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from flask import url_for, Response
55

66
from metadata_service.domain import metadata
7-
from metadata_service.domain.version import Version
7+
from metadata_service.domain.version import get_version_from_string
88

99
MOCKED_DATASTORE_VERSIONS = {
1010
"name": "SSB-RAIRD",
@@ -168,7 +168,7 @@ def test_get_data_structures(flask_app, mocker):
168168
)
169169

170170
spy.assert_called_with(
171-
["FNR", "AKT_ARBAP"], Version("3.2.1.0"), True, False
171+
["FNR", "AKT_ARBAP"], get_version_from_string("3.2.1.0"), True, False
172172
)
173173
assert response.headers["Content-Type"] == "application/json"
174174
assert response.json == mocked_data_structures
@@ -194,7 +194,7 @@ def test_get_data_structures_with_messagepack(flask_app, mocker):
194194
},
195195
)
196196
spy.assert_called_with(
197-
["FNR", "AKT_ARBAP"], Version("3.2.1.0"), True, False
197+
["FNR", "AKT_ARBAP"], get_version_from_string("3.2.1.0"), True, False
198198
)
199199
assert response.headers["Content-Type"] == "application/x-msgpack"
200200
assert msgpack.loads(response.data) == mocked_data_structures
@@ -246,7 +246,7 @@ def test_get_all_metadata(flask_app, mocker):
246246
"Accept": "application/json",
247247
},
248248
)
249-
spy.assert_called_with(Version("3.2.1.0"), False)
249+
spy.assert_called_with(get_version_from_string("3.2.1.0"), False)
250250
assert response.headers["Content-Type"] == "application/json"
251251
assert response.json == mocked_metadata_all
252252

@@ -276,7 +276,7 @@ def test_get_all_metadata_long_version_numbers(flask_app, mocker):
276276
"Accept": "application/json",
277277
},
278278
)
279-
spy.assert_called_with(Version("1234.5678.9012.0"), False)
279+
spy.assert_called_with(get_version_from_string("1234.5678.9012.0"), False)
280280
assert response.headers["Content-Type"] == "application/json"
281281
assert response.json == mocked_metadata_all
282282

@@ -313,7 +313,7 @@ def test_get_all_metadata_skip_code_lists(flask_app, mocker):
313313
"Accept": "application/json",
314314
},
315315
)
316-
spy.assert_called_with(Version("3.2.1.0"), True)
316+
spy.assert_called_with(get_version_from_string("3.2.1.0"), True)
317317
assert response.headers["Content-Type"] == "application/json"
318318
assert response.json == mocked_metadata_all
319319

@@ -339,7 +339,7 @@ def test_get_data_structures_skip_code_lists(flask_app, mocker):
339339
},
340340
)
341341
spy.assert_called_with(
342-
["FNR", "AKT_ARBAP"], Version("3.2.1.0"), True, True
342+
["FNR", "AKT_ARBAP"], get_version_from_string("3.2.1.0"), True, True
343343
)
344344
assert response.headers["Content-Type"] == "application/json"
345345
assert response.json == mocked_data_structures

tests/unit/domain/test_metadata.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from metadata_service.adapter import datastore
88
from metadata_service.config import environment
99
from metadata_service.domain import metadata
10-
from metadata_service.domain.version import Version
10+
from metadata_service.domain.version import get_version_from_string
1111
from metadata_service.exceptions.exceptions import (
1212
InvalidStorageFormatException,
1313
InvalidDraftVersionException,
@@ -37,7 +37,7 @@ def test_find_two_data_structures_with_attrs(mocker):
3737
)
3838
actual = metadata.find_data_structures(
3939
["TEST_PERSON_INCOME", "TEST_PERSON_PETS"],
40-
Version("1.0.0.0"),
40+
get_version_from_string("1.0.0.0"),
4141
True,
4242
skip_code_lists=False,
4343
)
@@ -64,7 +64,7 @@ def test_find_two_data_structures_without_attrs(mocker):
6464
)
6565
actual = metadata.find_data_structures(
6666
["TEST_PERSON_INCOME", "TEST_PERSON_PETS"],
67-
Version("1.0.0.0"),
67+
get_version_from_string("1.0.0.0"),
6868
False,
6969
skip_code_lists=False,
7070
)
@@ -90,7 +90,7 @@ def test_find_data_structures_no_name_filter(mocker):
9090
datastore, "get_metadata_all", return_value=mocked_metadata_all
9191
)
9292
actual = metadata.find_data_structures(
93-
[], Version("1.0.0.0"), True, skip_code_lists=False
93+
[], get_version_from_string("1.0.0.0"), True, skip_code_lists=False
9494
)
9595
assert len(actual) == 2
9696

@@ -237,7 +237,7 @@ def test_get_metadata_all_skip_code_list_and_missing_values(mocker):
237237
)
238238
filtered_metadata = (
239239
metadata.find_all_metadata_skip_code_list_and_missing_values(
240-
Version("1.0.0.0")
240+
get_version_from_string("1.0.0.0")
241241
)
242242
)
243243
_assert_code_list_and_missing_values(filtered_metadata["dataStructures"])
@@ -257,7 +257,7 @@ def test_find_all_metadata_skip_code_list_and_missing_values_invalid_model(
257257
)
258258
with pytest.raises(InvalidStorageFormatException) as e:
259259
metadata.find_all_metadata_skip_code_list_and_missing_values(
260-
Version("1.0.0.0")
260+
get_version_from_string("1.0.0.0")
261261
)
262262
assert "Invalid metadata format" == e.value.to_dict()["message"]
263263

@@ -269,7 +269,9 @@ def test_get_draft_metadata_all(mocker):
269269
mocker.patch.object(
270270
datastore, "get_metadata_all", return_value=mocked_metadata_all
271271
)
272-
filtered_metadata = metadata.find_all_metadata(Version("0.0.0.1608000000"))
272+
filtered_metadata = metadata.find_all_metadata(
273+
get_version_from_string("0.0.0.1608000000")
274+
)
273275

274276
assert "dataStructures" in filtered_metadata
275277

@@ -281,7 +283,9 @@ def test_get_draft_metadata_all_0_0_0_0(mocker):
281283
mocker.patch.object(
282284
datastore, "get_metadata_all", return_value=mocked_metadata_all
283285
)
284-
filtered_metadata = metadata.find_all_metadata(Version("0.0.0.0"))
286+
filtered_metadata = metadata.find_all_metadata(
287+
get_version_from_string("0.0.0.0")
288+
)
285289

286290
assert "dataStructures" in filtered_metadata
287291

@@ -295,7 +299,7 @@ def test_get_draft_metadata_all_invalid_draft_version(mocker):
295299
)
296300

297301
with pytest.raises(InvalidDraftVersionException) as e:
298-
metadata.find_all_metadata(Version("0.0.0.2"))
302+
metadata.find_all_metadata(get_version_from_string("0.0.0.2"))
299303

300304
assert "Requested draft version" in str(e)
301305

0 commit comments

Comments
 (0)