esgvoc 0.1.2__py3-none-any.whl → 0.2.1__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.
Potentially problematic release.
This version of esgvoc might be problematic. Click here for more details.
- esgvoc/api/__init__.py +15 -4
- esgvoc/api/data_descriptors/__init__.py +3 -0
- esgvoc/api/data_descriptors/directory_date.py +48 -0
- esgvoc/api/project_specs.py +82 -0
- esgvoc/api/projects.py +160 -130
- esgvoc/api/report.py +78 -50
- esgvoc/api/search.py +28 -10
- esgvoc/api/universe.py +17 -18
- esgvoc/apps/__init__.py +7 -0
- esgvoc/apps/drs/__init__.py +0 -16
- esgvoc/apps/drs/constants.py +2 -0
- esgvoc/apps/drs/generator.py +424 -0
- esgvoc/apps/drs/report.py +401 -0
- esgvoc/apps/drs/validator.py +332 -0
- esgvoc/cli/config.py +3 -0
- esgvoc/cli/drs.py +238 -0
- esgvoc/cli/get.py +1 -1
- esgvoc/cli/main.py +4 -3
- esgvoc/cli/status.py +13 -1
- esgvoc/cli/valid.py +1 -5
- esgvoc/core/db/models/mixins.py +7 -0
- esgvoc/core/db/models/project.py +3 -8
- esgvoc/core/db/project_ingestion.py +4 -1
- esgvoc/core/db/universe_ingestion.py +3 -3
- esgvoc/core/service/settings.py +17 -8
- esgvoc/core/service/settings.toml +11 -6
- esgvoc/core/service/settings_default.toml +11 -14
- esgvoc/core/service/state.py +19 -12
- esgvoc-0.2.1.dist-info/METADATA +58 -0
- {esgvoc-0.1.2.dist-info → esgvoc-0.2.1.dist-info}/RECORD +33 -26
- esgvoc-0.2.1.dist-info/licenses/LICENSE.txt +519 -0
- esgvoc/apps/drs/models.py +0 -43
- esgvoc/apps/drs/parser.py +0 -27
- esgvoc-0.1.2.dist-info/METADATA +0 -54
- {esgvoc-0.1.2.dist-info → esgvoc-0.2.1.dist-info}/WHEEL +0 -0
- {esgvoc-0.1.2.dist-info → esgvoc-0.2.1.dist-info}/entry_points.txt +0 -0
esgvoc/api/projects.py
CHANGED
|
@@ -8,7 +8,8 @@ from esgvoc.api._utils import (get_universe_session, instantiate_pydantic_term,
|
|
|
8
8
|
instantiate_pydantic_terms)
|
|
9
9
|
from esgvoc.api.report import (ProjectTermError, UniverseTermError,
|
|
10
10
|
ValidationError, ValidationReport)
|
|
11
|
-
from esgvoc.api.search import MatchingTerm, SearchSettings,
|
|
11
|
+
from esgvoc.api.search import MatchingTerm, SearchSettings, _create_str_comparison_expression
|
|
12
|
+
from esgvoc.api.project_specs import ProjectSpecs
|
|
12
13
|
from esgvoc.core.db.connection import DBConnection
|
|
13
14
|
from esgvoc.core.db.models.mixins import TermKind
|
|
14
15
|
from esgvoc.core.db.models.project import Collection, Project, PTerm
|
|
@@ -17,6 +18,24 @@ from pydantic import BaseModel
|
|
|
17
18
|
from sqlmodel import Session, and_, select
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
# [OPTIMIZATION]
|
|
22
|
+
_VALID_TERM_IN_COLLECTION_CACHE: dict[str, list[MatchingTerm]] = dict()
|
|
23
|
+
_VALID_VALUE_AGAINST_GIVEN_TERM_CACHE: dict[str, list[ValidationError]] = dict()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_project_specs(project_id: str) -> ProjectSpecs:
|
|
27
|
+
project_specs = find_project(project_id)
|
|
28
|
+
if not project_specs:
|
|
29
|
+
msg = f'Unable to find project {project_id}'
|
|
30
|
+
raise ValueError(msg)
|
|
31
|
+
try:
|
|
32
|
+
result = ProjectSpecs(**project_specs)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
msg = f'Unable to read specs in project {project_id}'
|
|
35
|
+
raise RuntimeError(msg) from e
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
|
|
20
39
|
def _get_project_connection(project_id: str) -> DBConnection|None:
|
|
21
40
|
return service.state_service.projects[project_id].db_connection
|
|
22
41
|
|
|
@@ -31,7 +50,7 @@ def _get_project_session_with_exception(project_id: str) -> Session:
|
|
|
31
50
|
def _resolve_term(term_composite_part: dict,
|
|
32
51
|
universe_session: Session,
|
|
33
52
|
project_session: Session) -> UTerm|PTerm:
|
|
34
|
-
|
|
53
|
+
# First find the term in the universe than in the current project
|
|
35
54
|
term_id = term_composite_part[esgvoc.core.constants.TERM_ID_JSON_KEY]
|
|
36
55
|
term_type = term_composite_part[esgvoc.core.constants.TERM_TYPE_JSON_KEY]
|
|
37
56
|
uterms = universe._find_terms_in_data_descriptor(data_descriptor_id=term_type,
|
|
@@ -156,9 +175,11 @@ def _valid_value_for_term_composite(value: str,
|
|
|
156
175
|
|
|
157
176
|
def _create_term_error(value: str, term: UTerm|PTerm) -> ValidationError:
|
|
158
177
|
if isinstance(term, UTerm):
|
|
159
|
-
return UniverseTermError(value, term
|
|
178
|
+
return UniverseTermError(value=value, term=term.specs, term_kind=term.kind,
|
|
179
|
+
data_descriptor_id=term.data_descriptor.id)
|
|
160
180
|
else:
|
|
161
|
-
return ProjectTermError(value, term
|
|
181
|
+
return ProjectTermError(value=value, term=term.specs, term_kind=term.kind,
|
|
182
|
+
collection_id=term.collection.id)
|
|
162
183
|
|
|
163
184
|
|
|
164
185
|
def _valid_value(value: str,
|
|
@@ -184,13 +205,11 @@ def _valid_value(value: str,
|
|
|
184
205
|
return result
|
|
185
206
|
|
|
186
207
|
|
|
187
|
-
def
|
|
188
|
-
if not value:
|
|
208
|
+
def _check_value(value: str) -> str:
|
|
209
|
+
if not value or value.isspace():
|
|
189
210
|
raise ValueError('value should be set')
|
|
190
|
-
if result:= value.strip():
|
|
191
|
-
return result
|
|
192
211
|
else:
|
|
193
|
-
|
|
212
|
+
return value
|
|
194
213
|
|
|
195
214
|
|
|
196
215
|
def _search_plain_term_and_valid_value(value: str,
|
|
@@ -223,26 +242,33 @@ def _valid_value_against_all_terms_of_collection(value: str,
|
|
|
223
242
|
|
|
224
243
|
|
|
225
244
|
def _valid_value_against_given_term(value: str,
|
|
245
|
+
project_id: str,
|
|
226
246
|
collection_id: str,
|
|
227
247
|
term_id: str,
|
|
228
248
|
universe_session: Session,
|
|
229
249
|
project_session: Session)\
|
|
230
250
|
-> list[ValidationError]:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
251
|
+
# [OPTIMIZATION]
|
|
252
|
+
key = value + project_id + collection_id + term_id
|
|
253
|
+
if key in _VALID_VALUE_AGAINST_GIVEN_TERM_CACHE:
|
|
254
|
+
result = _VALID_VALUE_AGAINST_GIVEN_TERM_CACHE[key]
|
|
255
|
+
else:
|
|
256
|
+
try:
|
|
257
|
+
terms = _find_terms_in_collection(collection_id,
|
|
258
|
+
term_id,
|
|
259
|
+
project_session,
|
|
260
|
+
None)
|
|
261
|
+
if terms:
|
|
262
|
+
term = terms[0]
|
|
263
|
+
result = _valid_value(value, term, universe_session, project_session)
|
|
264
|
+
else:
|
|
265
|
+
raise ValueError(f'unable to find term {term_id} ' +
|
|
266
|
+
f'in collection {collection_id}')
|
|
267
|
+
except Exception as e:
|
|
268
|
+
msg = f'unable to valid term {term_id} ' +\
|
|
269
|
+
f'in collection {collection_id}'
|
|
270
|
+
raise RuntimeError(msg) from e
|
|
271
|
+
_VALID_VALUE_AGAINST_GIVEN_TERM_CACHE[key] = result
|
|
246
272
|
return result
|
|
247
273
|
|
|
248
274
|
|
|
@@ -256,14 +282,14 @@ def valid_term(value: str,
|
|
|
256
282
|
a report that contains the possible errors.
|
|
257
283
|
|
|
258
284
|
Behavior based on the nature of the term:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
285
|
+
- plain term: the function try to match the value on the drs_name field.
|
|
286
|
+
- term pattern: the function try to match the value on the pattern field (regex).
|
|
287
|
+
- term composite:
|
|
288
|
+
- if the composite has got a separator, the function splits the value according to the\
|
|
289
|
+
separator of the term then it try to match every part of the composite\
|
|
290
|
+
with every split of the value.
|
|
291
|
+
- if the composite hasn't got a separator, the function aggregates the parts of the \
|
|
292
|
+
composite so as to compare it as a regex to the value.
|
|
267
293
|
|
|
268
294
|
If any of the provided ids (`project_id`, `collection_id` or `term_id`) is not found,
|
|
269
295
|
the function raises a ValueError.
|
|
@@ -280,12 +306,12 @@ def valid_term(value: str,
|
|
|
280
306
|
:rtype: ValidationReport
|
|
281
307
|
:raises ValueError: If any of the provided ids is not found
|
|
282
308
|
"""
|
|
283
|
-
value =
|
|
309
|
+
value = _check_value(value)
|
|
284
310
|
with get_universe_session() as universe_session, \
|
|
285
311
|
_get_project_session_with_exception(project_id) as project_session:
|
|
286
|
-
errors = _valid_value_against_given_term(value, collection_id, term_id,
|
|
312
|
+
errors = _valid_value_against_given_term(value, project_id, collection_id, term_id,
|
|
287
313
|
universe_session, project_session)
|
|
288
|
-
return ValidationReport(value, errors)
|
|
314
|
+
return ValidationReport(expression=value, errors=errors)
|
|
289
315
|
|
|
290
316
|
|
|
291
317
|
def _valid_term_in_collection(value: str,
|
|
@@ -294,28 +320,38 @@ def _valid_term_in_collection(value: str,
|
|
|
294
320
|
universe_session: Session,
|
|
295
321
|
project_session: Session) \
|
|
296
322
|
-> list[MatchingTerm]:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
None)
|
|
302
|
-
if collections:
|
|
303
|
-
collection = collections[0]
|
|
304
|
-
match collection.term_kind:
|
|
305
|
-
case TermKind.PLAIN:
|
|
306
|
-
term_id_found = _search_plain_term_and_valid_value(value, collection_id,
|
|
307
|
-
project_session)
|
|
308
|
-
if term_id_found:
|
|
309
|
-
result.append(MatchingTerm(project_id, collection_id, term_id_found))
|
|
310
|
-
case _:
|
|
311
|
-
term_ids_found = _valid_value_against_all_terms_of_collection(value, collection,
|
|
312
|
-
universe_session,
|
|
313
|
-
project_session)
|
|
314
|
-
for term_id_found in term_ids_found:
|
|
315
|
-
result.append(MatchingTerm(project_id, collection_id, term_id_found))
|
|
323
|
+
# [OPTIMIZATION]
|
|
324
|
+
key = value + project_id + collection_id
|
|
325
|
+
if key in _VALID_TERM_IN_COLLECTION_CACHE:
|
|
326
|
+
result = _VALID_TERM_IN_COLLECTION_CACHE[key]
|
|
316
327
|
else:
|
|
317
|
-
|
|
318
|
-
|
|
328
|
+
value = _check_value(value)
|
|
329
|
+
result = list()
|
|
330
|
+
collections = _find_collections_in_project(collection_id,
|
|
331
|
+
project_session,
|
|
332
|
+
None)
|
|
333
|
+
if collections:
|
|
334
|
+
collection = collections[0]
|
|
335
|
+
match collection.term_kind:
|
|
336
|
+
case TermKind.PLAIN:
|
|
337
|
+
term_id_found = _search_plain_term_and_valid_value(value, collection_id,
|
|
338
|
+
project_session)
|
|
339
|
+
if term_id_found:
|
|
340
|
+
result.append(MatchingTerm(project_id=project_id,
|
|
341
|
+
collection_id=collection_id,
|
|
342
|
+
term_id=term_id_found))
|
|
343
|
+
case _:
|
|
344
|
+
term_ids_found = _valid_value_against_all_terms_of_collection(value, collection,
|
|
345
|
+
universe_session,
|
|
346
|
+
project_session)
|
|
347
|
+
for term_id_found in term_ids_found:
|
|
348
|
+
result.append(MatchingTerm(project_id=project_id,
|
|
349
|
+
collection_id=collection_id,
|
|
350
|
+
term_id=term_id_found))
|
|
351
|
+
else:
|
|
352
|
+
msg = f'unable to find collection {collection_id}'
|
|
353
|
+
raise ValueError(msg)
|
|
354
|
+
_VALID_TERM_IN_COLLECTION_CACHE[key] = result
|
|
319
355
|
return result
|
|
320
356
|
|
|
321
357
|
|
|
@@ -328,14 +364,14 @@ def valid_term_in_collection(value: str,
|
|
|
328
364
|
returns the terms that the value matches.
|
|
329
365
|
|
|
330
366
|
Behavior based on the nature of the term:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
367
|
+
- plain term: the function try to match the value on the drs_name field.
|
|
368
|
+
- term pattern: the function try to match the value on the pattern field (regex).
|
|
369
|
+
- term composite:
|
|
370
|
+
- if the composite has got a separator, the function splits the value according to the \
|
|
371
|
+
separator of the term then it try to match every part of the composite \
|
|
372
|
+
with every split of the value.
|
|
373
|
+
- if the composite hasn't got a separator, the function aggregates the parts of the \
|
|
374
|
+
composite so as to compare it as a regex to the value.
|
|
339
375
|
|
|
340
376
|
If any of the provided ids (`project_id` or `collection_id`) is not found,
|
|
341
377
|
the function raises a ValueError.
|
|
@@ -374,14 +410,14 @@ def valid_term_in_project(value: str, project_id: str) -> list[MatchingTerm]:
|
|
|
374
410
|
returns the terms that the value matches.
|
|
375
411
|
|
|
376
412
|
Behavior based on the nature of the term:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
413
|
+
- plain term: the function try to match the value on the drs_name field.
|
|
414
|
+
- term pattern: the function try to match the value on the pattern field (regex).
|
|
415
|
+
- term composite:
|
|
416
|
+
- if the composite has got a separator, the function splits the value according to the \
|
|
417
|
+
separator of the term then it try to match every part of the composite \
|
|
418
|
+
with every split of the value.
|
|
419
|
+
- if the composite hasn't got a separator, the function aggregates the parts of the \
|
|
420
|
+
composite so as to compare it as a regex to the value.
|
|
385
421
|
|
|
386
422
|
If the `project_id` is not found, the function raises a ValueError.
|
|
387
423
|
|
|
@@ -404,14 +440,14 @@ def valid_term_in_all_projects(value: str) -> list[MatchingTerm]:
|
|
|
404
440
|
returns the terms that the value matches.
|
|
405
441
|
|
|
406
442
|
Behavior based on the nature of the term:
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
443
|
+
- plain term: the function try to match the value on the drs_name field.
|
|
444
|
+
- term pattern: the function try to match the value on the pattern field (regex).
|
|
445
|
+
- term composite:
|
|
446
|
+
- if the composite has got a separator, the function splits the value according to the \
|
|
447
|
+
separator of the term then it try to match every part of the composite \
|
|
448
|
+
with every split of the value.
|
|
449
|
+
- if the composite hasn't got a separator, the function aggregates the parts of the \
|
|
450
|
+
composite so as to compare it as a regex to the value.
|
|
415
451
|
|
|
416
452
|
:param value: A value to be validated
|
|
417
453
|
:type value: str
|
|
@@ -431,10 +467,10 @@ def _find_terms_in_collection(collection_id: str,
|
|
|
431
467
|
term_id: str,
|
|
432
468
|
session: Session,
|
|
433
469
|
settings: SearchSettings|None = None) -> Sequence[PTerm]:
|
|
434
|
-
|
|
435
|
-
where_expression =
|
|
436
|
-
|
|
437
|
-
|
|
470
|
+
# Settings only apply on the term_id comparison.
|
|
471
|
+
where_expression = _create_str_comparison_expression(field=PTerm.id,
|
|
472
|
+
value=term_id,
|
|
473
|
+
settings=settings)
|
|
438
474
|
statement = select(PTerm).join(Collection).where(Collection.id==collection_id,
|
|
439
475
|
where_expression)
|
|
440
476
|
results = session.exec(statement)
|
|
@@ -458,9 +494,9 @@ def find_terms_in_collection(project_id:str,
|
|
|
458
494
|
the function returns an empty list.
|
|
459
495
|
|
|
460
496
|
Behavior based on search type:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
497
|
+
- `EXACT` and absence of `settings`: returns zero or one Pydantic term instance in the list.
|
|
498
|
+
- `REGEX`, `LIKE`, `STARTS_WITH` and `ENDS_WITH`: returns zero, one or more \
|
|
499
|
+
Pydantic term instances in the list.
|
|
464
500
|
|
|
465
501
|
:param project_id: A project id
|
|
466
502
|
:type project_id: str
|
|
@@ -486,10 +522,10 @@ def _find_terms_from_data_descriptor_in_project(data_descriptor_id: str,
|
|
|
486
522
|
session: Session,
|
|
487
523
|
settings: SearchSettings|None = None) \
|
|
488
524
|
-> Sequence[PTerm]:
|
|
489
|
-
|
|
490
|
-
where_expression =
|
|
491
|
-
|
|
492
|
-
|
|
525
|
+
# Settings only apply on the term_id comparison.
|
|
526
|
+
where_expression = _create_str_comparison_expression(field=PTerm.id,
|
|
527
|
+
value=term_id,
|
|
528
|
+
settings=settings)
|
|
493
529
|
statement = select(PTerm).join(Collection).where(Collection.data_descriptor_id==data_descriptor_id,
|
|
494
530
|
where_expression)
|
|
495
531
|
results = session.exec(statement)
|
|
@@ -514,10 +550,10 @@ def find_terms_from_data_descriptor_in_project(project_id: str,
|
|
|
514
550
|
the function returns an empty list.
|
|
515
551
|
|
|
516
552
|
Behavior based on search type:
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
553
|
+
- `EXACT` and absence of `settings`: returns zero or one Pydantic term instance and \
|
|
554
|
+
collection id in the list.
|
|
555
|
+
- `REGEX`, `LIKE`, `STARTS_WITH` and `ENDS_WITH`: returns zero, one or more \
|
|
556
|
+
Pydantic term instances and collection ids in the list.
|
|
521
557
|
|
|
522
558
|
:param project_id: A project id
|
|
523
559
|
:type project_id: str
|
|
@@ -527,7 +563,7 @@ def find_terms_from_data_descriptor_in_project(project_id: str,
|
|
|
527
563
|
:type term_id: str
|
|
528
564
|
:param settings: The search settings
|
|
529
565
|
:type settings: SearchSettings|None
|
|
530
|
-
:returns: A list of tuple of Pydantic term instances and related collection ids.
|
|
566
|
+
:returns: A list of tuple of Pydantic term instances and related collection ids. \
|
|
531
567
|
Returns an empty list if no matches are found.
|
|
532
568
|
:rtype: list[tuple[BaseModel, str]]
|
|
533
569
|
"""
|
|
@@ -548,7 +584,7 @@ def find_terms_from_data_descriptor_in_project(project_id: str,
|
|
|
548
584
|
def find_terms_from_data_descriptor_in_all_projects(data_descriptor_id: str,
|
|
549
585
|
term_id: str,
|
|
550
586
|
settings: SearchSettings|None = None) \
|
|
551
|
-
|
|
587
|
+
-> list[tuple[list[tuple[BaseModel, str]], str]]:
|
|
552
588
|
"""
|
|
553
589
|
Finds one or more terms in all projects which are instances of the given data descriptor
|
|
554
590
|
in the universe, based on the specified search settings, in the given collection of a project.
|
|
@@ -561,10 +597,10 @@ def find_terms_from_data_descriptor_in_all_projects(data_descriptor_id: str,
|
|
|
561
597
|
the function returns an empty list.
|
|
562
598
|
|
|
563
599
|
Behavior based on search type:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
600
|
+
- `EXACT` and absence of `settings`: returns zero or one Pydantic term instance and \
|
|
601
|
+
collection id in the list.
|
|
602
|
+
- `REGEX`, `LIKE`, `STARTS_WITH` and `ENDS_WITH`: returns zero, one or more \
|
|
603
|
+
Pydantic term instances and collection ids in the list.
|
|
568
604
|
|
|
569
605
|
:param data_descriptor_id: A data descriptor
|
|
570
606
|
:type data_descriptor_id: str
|
|
@@ -572,26 +608,27 @@ def find_terms_from_data_descriptor_in_all_projects(data_descriptor_id: str,
|
|
|
572
608
|
:type term_id: str
|
|
573
609
|
:param settings: The search settings
|
|
574
610
|
:type settings: SearchSettings|None
|
|
575
|
-
:returns: A list of tuple of
|
|
611
|
+
:returns: A list of tuple of matching terms with their collection id, per project. \
|
|
576
612
|
Returns an empty list if no matches are found.
|
|
577
|
-
:rtype: list[tuple[BaseModel, str]]
|
|
613
|
+
:rtype: list[tuple[list[tuple[BaseModel, str]], str]]
|
|
578
614
|
"""
|
|
579
615
|
project_ids = get_all_projects()
|
|
580
|
-
result = list()
|
|
616
|
+
result: list[tuple[list[tuple[BaseModel, str]], str]] = list()
|
|
581
617
|
for project_id in project_ids:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
618
|
+
matching_terms = find_terms_from_data_descriptor_in_project(project_id,
|
|
619
|
+
data_descriptor_id,
|
|
620
|
+
term_id,
|
|
621
|
+
settings)
|
|
622
|
+
result.append((matching_terms, project_id))
|
|
586
623
|
return result
|
|
587
624
|
|
|
588
625
|
|
|
589
626
|
def _find_terms_in_project(term_id: str,
|
|
590
627
|
session: Session,
|
|
591
628
|
settings: SearchSettings|None) -> Sequence[PTerm]:
|
|
592
|
-
where_expression =
|
|
593
|
-
|
|
594
|
-
|
|
629
|
+
where_expression = _create_str_comparison_expression(field=PTerm.id,
|
|
630
|
+
value=term_id,
|
|
631
|
+
settings=settings)
|
|
595
632
|
statement = select(PTerm).where(where_expression)
|
|
596
633
|
results = session.exec(statement).all()
|
|
597
634
|
return results
|
|
@@ -668,8 +705,7 @@ def get_all_terms_in_collection(project_id: str,
|
|
|
668
705
|
:type project_id: str
|
|
669
706
|
:param collection_id: A collection id
|
|
670
707
|
:type collection_id: str
|
|
671
|
-
:returns: a list of Pydantic term instances.
|
|
672
|
-
Returns an empty list if no matches are found.
|
|
708
|
+
:returns: a list of Pydantic term instances. Returns an empty list if no matches are found.
|
|
673
709
|
:rtype: list[BaseModel]
|
|
674
710
|
"""
|
|
675
711
|
result = list()
|
|
@@ -688,9 +724,9 @@ def _find_collections_in_project(collection_id: str,
|
|
|
688
724
|
session: Session,
|
|
689
725
|
settings: SearchSettings|None) \
|
|
690
726
|
-> Sequence[Collection]:
|
|
691
|
-
where_exp =
|
|
692
|
-
|
|
693
|
-
|
|
727
|
+
where_exp = _create_str_comparison_expression(field=Collection.id,
|
|
728
|
+
value=collection_id,
|
|
729
|
+
settings=settings)
|
|
694
730
|
statement = select(Collection).where(where_exp)
|
|
695
731
|
results = session.exec(statement)
|
|
696
732
|
result = results.all()
|
|
@@ -713,9 +749,9 @@ def find_collections_in_project(project_id: str,
|
|
|
713
749
|
an empty list.
|
|
714
750
|
|
|
715
751
|
Behavior based on search type:
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
752
|
+
- `EXACT` and absence of `settings`: returns zero or one collection context in the list.
|
|
753
|
+
- `REGEX`, `LIKE`, `STARTS_WITH` and `ENDS_WITH`: returns zero, one or more \
|
|
754
|
+
collection contexts in the list.
|
|
719
755
|
|
|
720
756
|
:param project_id: A project id
|
|
721
757
|
:type project_id: str
|
|
@@ -723,8 +759,7 @@ def find_collections_in_project(project_id: str,
|
|
|
723
759
|
:type collection_id: str
|
|
724
760
|
:param settings: The search settings
|
|
725
761
|
:type settings: SearchSettings|None
|
|
726
|
-
:returns: A list of collection contexts.
|
|
727
|
-
Returns an empty list if no matches are found.
|
|
762
|
+
:returns: A list of collection contexts. Returns an empty list if no matches are found.
|
|
728
763
|
:rtype: list[dict]
|
|
729
764
|
"""
|
|
730
765
|
result = list()
|
|
@@ -753,8 +788,7 @@ def get_all_collections_in_project(project_id: str) -> list[str]:
|
|
|
753
788
|
|
|
754
789
|
:param project_id: A project id
|
|
755
790
|
:type project_id: str
|
|
756
|
-
:returns: A list of collection ids.
|
|
757
|
-
Returns an empty list if no matches are found.
|
|
791
|
+
:returns: A list of collection ids. Returns an empty list if no matches are found.
|
|
758
792
|
:rtype: list[str]
|
|
759
793
|
"""
|
|
760
794
|
result = list()
|
|
@@ -782,8 +816,7 @@ def get_all_terms_in_project(project_id: str) -> list[BaseModel]:
|
|
|
782
816
|
|
|
783
817
|
:param project_id: A project id
|
|
784
818
|
:type project_id: str
|
|
785
|
-
:returns: A list of Pydantic term instances.
|
|
786
|
-
Returns an empty list if no matches are found.
|
|
819
|
+
:returns: A list of Pydantic term instances. Returns an empty list if no matches are found.
|
|
787
820
|
:rtype: list[BaseModel]
|
|
788
821
|
"""
|
|
789
822
|
result = list()
|
|
@@ -819,8 +852,7 @@ def find_project(project_id: str) -> dict|None:
|
|
|
819
852
|
|
|
820
853
|
:param project_id: A project id to be found
|
|
821
854
|
:type project_id: str
|
|
822
|
-
:returns: The specs of the project found.
|
|
823
|
-
Returns `None` if no matches are found.
|
|
855
|
+
:returns: The specs of the project found. Returns `None` if no matches are found.
|
|
824
856
|
:rtype: dict|None
|
|
825
857
|
"""
|
|
826
858
|
result = None
|
|
@@ -848,7 +880,5 @@ if __name__ == "__main__":
|
|
|
848
880
|
print('OK')
|
|
849
881
|
else:
|
|
850
882
|
print(vr)
|
|
851
|
-
from esgvoc.api import BasicValidationErrorVisitor
|
|
852
|
-
visitor = BasicValidationErrorVisitor()
|
|
853
883
|
for error in vr.errors:
|
|
854
|
-
print(error
|
|
884
|
+
print(error)
|
esgvoc/api/report.py
CHANGED
|
@@ -1,80 +1,106 @@
|
|
|
1
|
+
from pydantic import BaseModel, computed_field
|
|
1
2
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Protocol
|
|
3
4
|
|
|
4
5
|
import esgvoc.core.constants as api_settings
|
|
5
6
|
from esgvoc.core.db.models.mixins import TermKind
|
|
6
|
-
from esgvoc.core.db.models.project import PTerm
|
|
7
|
-
from esgvoc.core.db.models.universe import UTerm
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
class ValidationErrorVisitor(
|
|
11
|
-
|
|
9
|
+
class ValidationErrorVisitor(Protocol):
|
|
10
|
+
"""
|
|
11
|
+
Specifications for a term validation error visitor.
|
|
12
|
+
"""
|
|
12
13
|
def visit_universe_term_error(self, error: "UniverseTermError") -> Any:
|
|
14
|
+
"""Visit a universe term error."""
|
|
13
15
|
pass
|
|
14
16
|
|
|
15
|
-
@abstractmethod
|
|
16
17
|
def visit_project_term_error(self, error: "ProjectTermError") -> Any:
|
|
18
|
+
"""Visit a project term error."""
|
|
17
19
|
pass
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
class
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
f"does not validate the given value '{error.value}'"
|
|
31
|
-
return result
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class ValidationError(ABC):
|
|
35
|
-
def __init__(self,
|
|
36
|
-
value: str):
|
|
37
|
-
self.value: str = value
|
|
22
|
+
class ValidationError(BaseModel, ABC):
|
|
23
|
+
"""
|
|
24
|
+
Generic class for the term validation error.
|
|
25
|
+
"""
|
|
26
|
+
value: str
|
|
27
|
+
"""The given value that is invalid."""
|
|
28
|
+
term: dict
|
|
29
|
+
"""JSON specification of the term."""
|
|
30
|
+
term_kind: TermKind
|
|
31
|
+
"""The kind of term."""
|
|
38
32
|
|
|
39
33
|
@abstractmethod
|
|
40
34
|
def accept(self, visitor: ValidationErrorVisitor) -> Any:
|
|
35
|
+
"""
|
|
36
|
+
Accept a validation error visitor.
|
|
37
|
+
|
|
38
|
+
:param visitor: The validation error visitor.
|
|
39
|
+
:type visitor: ValidationErrorVisitor
|
|
40
|
+
:return: Depending on the visitor.
|
|
41
|
+
:rtype: Any
|
|
42
|
+
"""
|
|
41
43
|
pass
|
|
42
44
|
|
|
43
45
|
class UniverseTermError(ValidationError):
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self.data_descriptor_id: str = term.data_descriptor.id
|
|
46
|
+
"""
|
|
47
|
+
A validation error on a term from the universe.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
data_descriptor_id: str
|
|
51
|
+
"""The data descriptor that the term belongs."""
|
|
51
52
|
|
|
52
53
|
def accept(self, visitor: ValidationErrorVisitor) -> Any:
|
|
53
54
|
return visitor.visit_universe_term_error(self)
|
|
55
|
+
|
|
56
|
+
def __str__(self) -> str:
|
|
57
|
+
term_id = self.term[api_settings.TERM_ID_JSON_KEY]
|
|
58
|
+
result = f"The term {term_id} from the data descriptor {self.data_descriptor_id} "+\
|
|
59
|
+
f"does not validate the given value '{self.value}'"
|
|
60
|
+
return result
|
|
61
|
+
def __repr__(self) -> str:
|
|
62
|
+
return self.__str__()
|
|
54
63
|
|
|
55
64
|
|
|
56
65
|
class ProjectTermError(ValidationError):
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
self.collection_id: str = term.collection.id
|
|
66
|
+
"""
|
|
67
|
+
A validation error on a term from a project.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
collection_id: str
|
|
71
|
+
"""The collection id that the term belongs"""
|
|
64
72
|
|
|
65
73
|
def accept(self, visitor: ValidationErrorVisitor) -> Any:
|
|
66
74
|
return visitor.visit_project_term_error(self)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
self.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
term_id = self.term[api_settings.TERM_ID_JSON_KEY]
|
|
78
|
+
result = f"The term {term_id} from the collection {self.collection_id} "+\
|
|
79
|
+
f"does not validate the given value '{self.value}'"
|
|
80
|
+
return result
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
return self.__str__()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ValidationReport(BaseModel):
|
|
86
|
+
"""
|
|
87
|
+
Term validation report.
|
|
88
|
+
"""
|
|
89
|
+
expression: str
|
|
90
|
+
"""The given expression."""
|
|
91
|
+
errors: list[ValidationError]
|
|
92
|
+
"""The validation errors."""
|
|
93
|
+
@computed_field # type: ignore
|
|
94
|
+
@property
|
|
95
|
+
def nb_errors(self) -> int:
|
|
96
|
+
"""The number of validation errors."""
|
|
97
|
+
return len(self.errors) if self.errors else 0
|
|
98
|
+
@computed_field # type: ignore
|
|
99
|
+
@property
|
|
100
|
+
def validated(self) -> bool:
|
|
101
|
+
"""The expression is validated or not."""
|
|
102
|
+
return False if self.errors else True
|
|
103
|
+
|
|
78
104
|
|
|
79
105
|
def __len__(self) -> int:
|
|
80
106
|
return self.nb_errors
|
|
@@ -82,5 +108,7 @@ class ValidationReport:
|
|
|
82
108
|
def __bool__(self) -> bool:
|
|
83
109
|
return self.validated
|
|
84
110
|
|
|
111
|
+
def __str__(self) -> str:
|
|
112
|
+
return f"'{self.expression}' has {self.nb_errors} error(s)"
|
|
85
113
|
def __repr__(self) -> str:
|
|
86
|
-
return self.
|
|
114
|
+
return self.__str__()
|