mash-core 0.9.0__py3-none-any.whl
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.
- mash/__init__.py +426 -0
- mash/core/__init__.py +0 -0
- mash/core/catalog/__init__.py +1 -0
- mash/core/catalog/api.py +76 -0
- mash/core/catalog/frontend/assets/entry.client-Bl1bODN_.js +13 -0
- mash/core/catalog/frontend/assets/helpers-CvVoHxcP.js +19 -0
- mash/core/catalog/frontend/assets/home-D7SI6u38.js +1 -0
- mash/core/catalog/frontend/assets/index-Dh-O-te8.js +26 -0
- mash/core/catalog/frontend/assets/manifest-f8f091fe.js +1 -0
- mash/core/catalog/frontend/assets/root-BE3ctyoe.css +1 -0
- mash/core/catalog/frontend/assets/root-CIOxwHW_.js +1 -0
- mash/core/catalog/frontend/favicon.ico +0 -0
- mash/core/catalog/frontend/index.html +13 -0
- mash/core/catalog/readme.md +3 -0
- mash/core/compat/__init__.py +0 -0
- mash/core/compat/maplib/__init__.py +1 -0
- mash/core/compat/maplib/serialization_helper.py +249 -0
- mash/core/config.py +35 -0
- mash/core/linkml/__init__.py +2 -0
- mash/core/linkml/schema.py +112 -0
- mash/core/linkml/shapes.py +49 -0
- mash/core/logging.py +47 -0
- mash/core/maplib.py +8 -0
- mash/core/metadata/__init__.py +1 -0
- mash/core/metadata/models.py +291 -0
- mash/core/mime.py +50 -0
- mash/core/models.py +71 -0
- mash/core/ns.py +53 -0
- mash/core/profiles/__init__.py +2 -0
- mash/core/profiles/mash_profile.py +53 -0
- mash/core/profiles/metadata.py +31 -0
- mash/core/utilities/__init__.py +3 -0
- mash/core/utilities/add_prefix_url.py +34 -0
- mash/core/utilities/attributes.py +106 -0
- mash/core/utilities/azure.py +35 -0
- mash/core/utilities/dataset.py +10 -0
- mash/core/utilities/dynamic.py +11 -0
- mash/core/utilities/files.py +24 -0
- mash/core/utilities/identifiers.py +11 -0
- mash/core/utilities/organizations.py +9 -0
- mash/core/utilities/profiles.py +23 -0
- mash/core/utilities/ranges.py +28 -0
- mash/core/utilities/resource_descriptors.py +5 -0
- mash/core/utilities/semantic_enum.py +40 -0
- mash/core/utilities/user.py +22 -0
- mash/core/validation.py +110 -0
- mash/orm/__init__.py +4 -0
- mash/orm/query/__init__.py +2 -0
- mash/orm/query/filters.py +235 -0
- mash/orm/query/query.py +149 -0
- mash/orm/read.py +413 -0
- mash/orm/update.py +253 -0
- mash/orm/write.py +186 -0
- mash/repositories/__init__.py +2 -0
- mash/repositories/mesh/__init__.py +1 -0
- mash/repositories/mesh/blob_mesh_repository.py +232 -0
- mash/repositories/mesh/filesystem_mesh_repository.py +146 -0
- mash/repositories/mesh/mesh_repository.py +52 -0
- mash/repositories/mesh/multi_repository_view.py +157 -0
- mash/repositories/mesh/repository_collection.py +48 -0
- mash/repositories/mesh/utilities.py +69 -0
- mash/repositories/models/__init__.py +1 -0
- mash/repositories/models/model_registry.py +78 -0
- mash/repositories/namespace/__init__.py +1 -0
- mash/repositories/namespace/namespace.py +122 -0
- mash_core-0.9.0.dist-info/METADATA +198 -0
- mash_core-0.9.0.dist-info/RECORD +69 -0
- mash_core-0.9.0.dist-info/WHEEL +4 -0
- mash_core-0.9.0.dist-info/licenses/LICENSE +21 -0
mash/__init__.py
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
_SHAPE_NS = "urn:graph:shapes"
|
|
2
|
+
_ONTO_NS = "urn:graph:ontology"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class _STATIC:
|
|
6
|
+
maplib_compatible_mimetypes = [
|
|
7
|
+
"application/n-triples",
|
|
8
|
+
"text/turtle",
|
|
9
|
+
"application/rdf+xml",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import atexit
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
from enum import StrEnum
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from tempfile import TemporaryDirectory, mkdtemp
|
|
19
|
+
from typing import Literal
|
|
20
|
+
from warnings import warn
|
|
21
|
+
|
|
22
|
+
from cachetools import LRUCache
|
|
23
|
+
from maplib import Model
|
|
24
|
+
|
|
25
|
+
from mash.core.config import Config
|
|
26
|
+
from mash.core.logging import color_logger
|
|
27
|
+
from mash.repositories.mesh.mesh_repository import MeshRepository
|
|
28
|
+
from mash.repositories.mesh.repository_collection import RepositoryCollection
|
|
29
|
+
from mash.repositories.namespace.namespace import (
|
|
30
|
+
DictBackedNamespaceRepository,
|
|
31
|
+
NamespaceRepository,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = color_logger()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _MashImpl:
|
|
38
|
+
namespaces: NamespaceRepository
|
|
39
|
+
config: Config
|
|
40
|
+
|
|
41
|
+
_static_repo_collection: RepositoryCollection
|
|
42
|
+
_static_repo_view: MeshRepository
|
|
43
|
+
_live_repo_view: MeshRepository
|
|
44
|
+
_live_repo_collection: RepositoryCollection
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.reset()
|
|
48
|
+
|
|
49
|
+
def reset(self):
|
|
50
|
+
self.config = Config()
|
|
51
|
+
self._static_repo_collection = RepositoryCollection()
|
|
52
|
+
self._live_repo_collection = RepositoryCollection()
|
|
53
|
+
self.reset_temp_directory()
|
|
54
|
+
self.namespaces = DictBackedNamespaceRepository()
|
|
55
|
+
|
|
56
|
+
def repositories_ordered(self, default=True, non_default=True) -> list[MeshRepository]:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def active_repository(self) -> MeshRepository:
|
|
61
|
+
return self._live_repo_collection.active_repository()
|
|
62
|
+
|
|
63
|
+
def reset_temp_directory(self):
|
|
64
|
+
if hasattr(self, "_tmpdir_cls") and isinstance(self._tmpdir_cls, TemporaryDirectory):
|
|
65
|
+
self._tmpdir_cls.cleanup()
|
|
66
|
+
|
|
67
|
+
tempdir = self.config.data_storage_directory / "temp"
|
|
68
|
+
if not tempdir.exists():
|
|
69
|
+
tempdir.mkdir(parents=True)
|
|
70
|
+
self._tmpdir_cls = TemporaryDirectory(
|
|
71
|
+
"mash",
|
|
72
|
+
delete=False,
|
|
73
|
+
dir=tempdir,
|
|
74
|
+
)
|
|
75
|
+
self._tmpdir = self._tmpdir_cls.name
|
|
76
|
+
|
|
77
|
+
def static_repository(self):
|
|
78
|
+
return self._static_repo_view
|
|
79
|
+
|
|
80
|
+
def add_repository(self, key, repository: MeshRepository):
|
|
81
|
+
self._live_repo_collection[key] = repository
|
|
82
|
+
|
|
83
|
+
def remove_repository(self, key):
|
|
84
|
+
self._live_repo_collection.pop(key)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_mash = _MashImpl()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _get_impl() -> _MashImpl:
|
|
91
|
+
return _mash
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
from mash.core.metadata import models
|
|
95
|
+
from mash.repositories.models import ModelRegistry, find_semantic_models_in_module
|
|
96
|
+
|
|
97
|
+
_model_registry = ModelRegistry()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def clean_up_tempdir():
|
|
101
|
+
_mash._tmpdir_cls.cleanup()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
atexit.register(clean_up_tempdir)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
from mash.core.metadata.models import SemanticModel
|
|
108
|
+
from mash.core.models import IRI, Predicate, ReverseRelation
|
|
109
|
+
from mash.orm import instantiate, serialize
|
|
110
|
+
|
|
111
|
+
# Repository stuff below, could make into optional package
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_active_repository() -> MeshRepository:
|
|
115
|
+
return _mash.active_repository
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _where_is_it(arg: models.SemanticModel, exclude: list):
|
|
119
|
+
for repository in _mash._live_repo_collection.values():
|
|
120
|
+
if repository in exclude:
|
|
121
|
+
continue
|
|
122
|
+
res = repository.has_data(arg.identifier)
|
|
123
|
+
if res:
|
|
124
|
+
return repository
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def repository_add(key: str, repository: MeshRepository, ignore_if_exists=False):
|
|
128
|
+
if ignore_if_exists and key in _mash._live_repo_collection:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
_mash.add_repository(key, repository)
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def repository_remove(key: str):
|
|
136
|
+
_mash.remove_repository(key)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _ActiveRepository() -> None:
|
|
140
|
+
"""Vanguard statement to represent the active repostory"""
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def write_data(data: bytes, resource: models.Distribution | str, repo=_ActiveRepository()):
|
|
145
|
+
if repo is None:
|
|
146
|
+
repo = _mash.active_repository
|
|
147
|
+
|
|
148
|
+
with repo.open(resource, mode="wb") as file:
|
|
149
|
+
file.write(data)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def copy(
|
|
153
|
+
instances: list[models.SemanticModel] | models.SemanticModel,
|
|
154
|
+
dest: MeshRepository,
|
|
155
|
+
source: MeshRepository | Literal["any"] = _ActiveRepository(),
|
|
156
|
+
filter_mimetype=None,
|
|
157
|
+
):
|
|
158
|
+
"""Copies all metadata and data-data contained in a set of instances from one repository to another"""
|
|
159
|
+
if not isinstance(instances, list):
|
|
160
|
+
instances = [instances]
|
|
161
|
+
|
|
162
|
+
if source is None:
|
|
163
|
+
source = _mash.active_repository
|
|
164
|
+
|
|
165
|
+
# 1. Copy metadata
|
|
166
|
+
dest.update_metadata(*instances)
|
|
167
|
+
|
|
168
|
+
# 2. Copy data-data
|
|
169
|
+
for instance in instances:
|
|
170
|
+
is_distribution = isinstance(instance, models.Distribution) or issubclass(instance.__class__, models.Distribution)
|
|
171
|
+
|
|
172
|
+
if source == "any":
|
|
173
|
+
if isinstance(instance, models.Dataset):
|
|
174
|
+
_dist = instance.distributions[0]
|
|
175
|
+
else:
|
|
176
|
+
_dist = instance
|
|
177
|
+
source_repository = _where_is_it(_dist, exclude=[dest])
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
source_repository = source
|
|
181
|
+
|
|
182
|
+
if source_repository is None:
|
|
183
|
+
# This means the object doesn't exist anywhere yet, so there is no data to copy
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
if is_distribution:
|
|
187
|
+
_copy_dist_data(instance, source_repository, dest)
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
elif isinstance(instance, models.Dataset):
|
|
191
|
+
for dist in instance.distributions:
|
|
192
|
+
if filter_mimetype and dist.mime_type != filter_mimetype:
|
|
193
|
+
continue
|
|
194
|
+
_copy_dist_data(dist, source_repository, dest)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _copy_dist_data(distribution: models.Distribution, source_repo: MeshRepository, dest_repo: MeshRepository):
|
|
198
|
+
try:
|
|
199
|
+
with source_repo.open(distribution) as source_fp:
|
|
200
|
+
with dest_repo.open(distribution, "wb") as dest_fp:
|
|
201
|
+
shutil.copyfileobj(source_fp, dest_fp)
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
logger.warning("No data found for distribution")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def save(*args: models.SemanticModel, repo: MeshRepository = _ActiveRepository()):
|
|
207
|
+
"""Updates or saves a model in active storage"""
|
|
208
|
+
if repo is None:
|
|
209
|
+
repo = _mash.active_repository
|
|
210
|
+
repo.update_metadata(*args)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def fetch(identifier: str, repo: MeshRepository | Literal["any"] = _ActiveRepository()) -> models.SemanticModel | None:
|
|
214
|
+
"""Fetches a resource from any repository"""
|
|
215
|
+
if repo is None:
|
|
216
|
+
return _mash._live_repo_view.fetch(identifier, repo="active")
|
|
217
|
+
|
|
218
|
+
if repo == "any":
|
|
219
|
+
return _mash._live_repo_view.fetch(identifier, repo="any")
|
|
220
|
+
|
|
221
|
+
return repo.fetch(identifier)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def safe_fetch(identifier: str, repo: MeshRepository | Literal["any"] = _ActiveRepository()) -> models.SemanticModel | None:
|
|
225
|
+
"""Fetches a resource from any repository, returning None on error"""
|
|
226
|
+
try:
|
|
227
|
+
return fetch(identifier, repo)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
logger.warning(f"Failed to fetch {identifier} from repository {repo}: {e}")
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def delete(*args: models.SemanticModel, repo=_ActiveRepository()):
|
|
234
|
+
"""Deletes metadata and data attached to models from temporary and local folders"""
|
|
235
|
+
if repo is None:
|
|
236
|
+
repo = _mash.active_repository
|
|
237
|
+
|
|
238
|
+
repo.delete(*args)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def metadata_graph(format="ntriples") -> Model:
|
|
242
|
+
"""Fetch metadata from all repositories and return a common RDf graph with all of it"""
|
|
243
|
+
return _mash._live_repo_view.metadata_graph()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class data:
|
|
247
|
+
def repositories_with_data(distribution_id) -> list[MeshRepository]:
|
|
248
|
+
live_repos = _mash._live_repo_collection.values()
|
|
249
|
+
return [r for r in live_repos if r.has_data(distribution_id)]
|
|
250
|
+
|
|
251
|
+
def exists_locally(distribution_id) -> bool:
|
|
252
|
+
return known_repository("local").has_data(distribution_id)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
from mash.core.utilities.dataset import distribution_with_mimetype
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def read_data(
|
|
259
|
+
source: models.Distribution | models.Dataset | str, mime_type: str = None, repo: MeshRepository | Literal["any"] = _ActiveRepository()
|
|
260
|
+
) -> bytes:
|
|
261
|
+
"""Fetches data from any repository"""
|
|
262
|
+
if repo is None:
|
|
263
|
+
repo = _mash.active_repository
|
|
264
|
+
|
|
265
|
+
# Resolve distribution
|
|
266
|
+
if isinstance(source, str):
|
|
267
|
+
source = fetch(source, repo)
|
|
268
|
+
if source is None:
|
|
269
|
+
raise FileNotFoundError("The distribution was not found in provided repo")
|
|
270
|
+
|
|
271
|
+
if isinstance(source, models.Dataset):
|
|
272
|
+
if mime_type is None:
|
|
273
|
+
raise ValueError("When trying to fetch data from a dataset, the attribute mime_type is required")
|
|
274
|
+
|
|
275
|
+
source = distribution_with_mimetype(source, mime_type)
|
|
276
|
+
if source is None:
|
|
277
|
+
raise ValueError("No distribution found with provided mime_type")
|
|
278
|
+
|
|
279
|
+
distribution_id = source.identifier
|
|
280
|
+
|
|
281
|
+
# Fetch data
|
|
282
|
+
if repo != "any":
|
|
283
|
+
with repo.open(distribution_id) as file:
|
|
284
|
+
return file.read()
|
|
285
|
+
|
|
286
|
+
with _mash._live_repo_view.open(distribution_id, mode="rb", repo="any") as file:
|
|
287
|
+
return file.read()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
from mash.repositories.mesh.filesystem_mesh_repository import FilesystemMeshRepository
|
|
291
|
+
from mash.repositories.mesh.multi_repository_view import MultiRepositoryView
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def add_local_repository(key, ignore_if_exists=False):
|
|
295
|
+
path: Path = _mash.config.data_storage_directory / key
|
|
296
|
+
path.mkdir(exist_ok=True)
|
|
297
|
+
if repository_add(
|
|
298
|
+
key,
|
|
299
|
+
FilesystemMeshRepository(
|
|
300
|
+
path,
|
|
301
|
+
),
|
|
302
|
+
ignore_if_exists=ignore_if_exists,
|
|
303
|
+
):
|
|
304
|
+
logger.info(f"Added local repository '{key}' at {path}")
|
|
305
|
+
else:
|
|
306
|
+
logger.info(f"Repository with key '{key}' already exists")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _setup_default_repositories():
|
|
310
|
+
_mash._static_repo_view = MultiRepositoryView(_mash._static_repo_collection, read_only=True)
|
|
311
|
+
_mash._live_repo_collection["static"] = _mash._static_repo_view
|
|
312
|
+
_mash._live_repo_view = MultiRepositoryView(_mash._live_repo_collection)
|
|
313
|
+
_mash.reset_temp_directory()
|
|
314
|
+
temp_repo = FilesystemMeshRepository(Path(_mash._tmpdir))
|
|
315
|
+
_mash.add_repository("temp", temp_repo)
|
|
316
|
+
_mash._live_repo_collection.activate_layer(temp_repo)
|
|
317
|
+
|
|
318
|
+
storage_dir = _mash.config.data_storage_directory
|
|
319
|
+
if not storage_dir.exists():
|
|
320
|
+
storage_dir.mkdir()
|
|
321
|
+
|
|
322
|
+
_mash.add_repository(
|
|
323
|
+
"local",
|
|
324
|
+
FilesystemMeshRepository(
|
|
325
|
+
_mash.config.data_storage_directory,
|
|
326
|
+
),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
from mash.repositories.mesh.utilities import load_configured_blob_repositories
|
|
331
|
+
|
|
332
|
+
_setup_default_repositories()
|
|
333
|
+
load_configured_blob_repositories()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class _UseRepositoryImpl:
|
|
337
|
+
"""
|
|
338
|
+
Activates a repository as the default one,
|
|
339
|
+
works as both a context manager and a function.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
def __init__(self, repo: MeshRepository | str):
|
|
343
|
+
if isinstance(repo, str):
|
|
344
|
+
repo = _mash._live_repo_collection[repo]
|
|
345
|
+
|
|
346
|
+
_mash._live_repo_collection.activate_layer(repo)
|
|
347
|
+
|
|
348
|
+
def __enter__(self):
|
|
349
|
+
"""Doesn't need to do anything"""
|
|
350
|
+
|
|
351
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
352
|
+
_mash._live_repo_collection.pop_layer()
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
use_repository = _UseRepositoryImpl
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def known_repository(kind: Literal["temp", "local", "static", "default"]) -> MeshRepository:
|
|
359
|
+
if kind == "default":
|
|
360
|
+
return _mash.active_repository
|
|
361
|
+
|
|
362
|
+
return _mash._live_repo_collection[kind]
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
from mash.core.maplib import model_from_dataset, serialize_model_to_dataset
|
|
366
|
+
from mash.core.validation import validate_dataset
|
|
367
|
+
from mash.orm.query import query, strict_query
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def ns():
|
|
371
|
+
return _mash.namespaces
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
from mash.core.profiles import MashProfile, MetadataProfile
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def globally_use_profile(profile: MashProfile):
|
|
378
|
+
_mash.namespaces.add_layer(profile.namespaces)
|
|
379
|
+
_model_registry.add_layer(profile.models)
|
|
380
|
+
if profile.static_resources is not None:
|
|
381
|
+
_mash._static_repo_collection[profile.identifier] = profile.static_resources
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
globally_use_profile(MetadataProfile)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_repository(key: str) -> MeshRepository | None:
|
|
388
|
+
return _mash._live_repo_collection.get(key)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
K3_DIRECTORY = os.getenv("K3_DIRECTORY", None)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def k3(filename) -> Path:
|
|
395
|
+
"""This is a utility to make it easy to write and read files from K3_DIRECTORY
|
|
396
|
+
rather than the working directory
|
|
397
|
+
"""
|
|
398
|
+
try:
|
|
399
|
+
k3_directory = Path(os.environ["K3_DIRECTORY"])
|
|
400
|
+
assert k3_directory.exists(), "The K3_DIRECTORY pointed to must exist"
|
|
401
|
+
work_dir = k3_directory
|
|
402
|
+
if not work_dir.exists():
|
|
403
|
+
os.mkdir(work_dir)
|
|
404
|
+
logger.info("Created directory " + str(work_dir))
|
|
405
|
+
except KeyError:
|
|
406
|
+
raise ValueError("Your must have the environment K3_DIRECTORY set pointing to a safe folder for storage of sensitive data")
|
|
407
|
+
|
|
408
|
+
return work_dir / filename
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
import importlib
|
|
412
|
+
|
|
413
|
+
for extension in _mash.config.extensions_enabled:
|
|
414
|
+
importlib.import_module(extension)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _reset():
|
|
418
|
+
clean_up_tempdir()
|
|
419
|
+
_mash.reset()
|
|
420
|
+
_model_registry.reset()
|
|
421
|
+
_setup_default_repositories()
|
|
422
|
+
load_configured_blob_repositories()
|
|
423
|
+
globally_use_profile(MetadataProfile)
|
|
424
|
+
for extension in _mash.config.extensions_enabled:
|
|
425
|
+
_mod = importlib.import_module(extension)
|
|
426
|
+
_mod.module_enable()
|
mash/core/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .api import app
|
mash/core/catalog/api.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import webbrowser
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
|
|
5
|
+
from cachetools import LRUCache, cached
|
|
6
|
+
from fastapi import APIRouter, FastAPI
|
|
7
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
8
|
+
from fastapi.staticfiles import StaticFiles
|
|
9
|
+
|
|
10
|
+
import mash
|
|
11
|
+
from mash import models
|
|
12
|
+
from mash.core.utilities.user import get_current_agent
|
|
13
|
+
from mash.orm import filters
|
|
14
|
+
|
|
15
|
+
HOST = ""
|
|
16
|
+
DATASET_PREFIX = "/dataset"
|
|
17
|
+
GRID_MODEL_PREFIX = "/models"
|
|
18
|
+
SERVICE_PREFIX = "/service"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@asynccontextmanager
|
|
22
|
+
async def lifespan(app: FastAPI):
|
|
23
|
+
if os.getenv("DEV_WP") == "true":
|
|
24
|
+
webbrowser.open_new_tab("http://localhost:8234/catalog")
|
|
25
|
+
yield
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
app = FastAPI(title="Mash API", lifespan=lifespan)
|
|
29
|
+
|
|
30
|
+
app.add_middleware(
|
|
31
|
+
CORSMiddleware,
|
|
32
|
+
allow_origins=["*"],
|
|
33
|
+
allow_credentials=True,
|
|
34
|
+
allow_methods=["*"],
|
|
35
|
+
allow_headers=["*"],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
catalog = models.Catalog(
|
|
39
|
+
identifier="urn:mash:local_catalog",
|
|
40
|
+
title="Mash data catalog",
|
|
41
|
+
description="An automatically generated catalog of resources found in Mash",
|
|
42
|
+
publisher=get_current_agent(),
|
|
43
|
+
contact=models.ContactGroup(identifier="mail@example.com", name="Example", mail="mail@example.com"),
|
|
44
|
+
datasets=[f"{HOST}{DATASET_PREFIX}/{d.identifier}" for d in mash.query(filters.isKind(models.Dataset))],
|
|
45
|
+
dataservices=[f"{HOST}{SERVICE_PREFIX}/{d.identifier}" for d in mash.query(filters.isKind(models.DataService))],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
app.mount(
|
|
50
|
+
"/catalog",
|
|
51
|
+
StaticFiles(directory="mash/core/catalog/frontend", html=True),
|
|
52
|
+
name="Data catalog UI",
|
|
53
|
+
)
|
|
54
|
+
app.mount("/assets", StaticFiles(directory="mash/core/catalog/frontend/assets"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.get("/dcat/catalog", tags=["Catalog"])
|
|
58
|
+
def describe_data_catalog() -> models.Catalog:
|
|
59
|
+
return catalog
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
datasets = APIRouter(prefix=DATASET_PREFIX, tags=["Datasets"])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@datasets.get("")
|
|
66
|
+
@cached(cache=LRUCache(8))
|
|
67
|
+
def list_datasets() -> list[models.Dataset]:
|
|
68
|
+
return mash.query(filters.isKind(models.Dataset))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@datasets.get("/{id}")
|
|
72
|
+
def describe_dataset(id: str) -> models.Dataset:
|
|
73
|
+
return mash.query(filters.isKind(models.Dataset), filters.hasIdentifier(id))[0]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
app.include_router(datasets)
|