Skip to content

Commit 0279a69

Browse files
committed
fix: robust synthetic property handling for score sets
- Refactored ScoreSet view models to ensure robust handling of synthetic and required fields for both ORM and dict contexts. - Added tests to verify all key attributes and synthetic properties are correctly handled in both construction modes. - Ensured creation from both dict and ORM contexts, mirroring the approach used for other models.
1 parent 0681df5 commit 0279a69

File tree

2 files changed

+49
-26
lines changed

2 files changed

+49
-26
lines changed

src/mavedb/view_models/score_set.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,11 @@ class Config:
311311
arbitrary_types_allowed = True
312312

313313
# These 'synthetic' fields are generated from other model properties. Transform data from other properties as needed, setting
314-
# the appropriate field on the model itself. Then, proceed with Pydantic ingestion once fields are created.
314+
# the appropriate field on the model itself. Then, proceed with Pydantic ingestion once fields are created. Only perform these
315+
# transformations if the relevant attributes are present on the input data (i.e., when creating from an ORM object).
315316
@model_validator(mode="before")
316317
def generate_primary_and_secondary_publications(cls, data: Any):
317-
if not hasattr(data, "primary_publication_identifiers") or not hasattr(
318-
data, "secondary_publication_identifiers"
319-
):
318+
if hasattr(data, "publication_identifier_associations"):
320319
try:
321320
publication_identifiers = transform_record_publication_identifiers(
322321
data.publication_identifier_associations
@@ -327,9 +326,9 @@ def generate_primary_and_secondary_publications(cls, data: Any):
327326
data.__setattr__(
328327
"secondary_publication_identifiers", publication_identifiers["secondary_publication_identifiers"]
329328
)
330-
except AttributeError as exc:
329+
except (AttributeError, KeyError) as exc:
331330
raise ValidationError(
332-
f"Unable to create {cls.__name__} without attribute: {exc}." # type: ignore
331+
f"Unable to coerce publication identifier attributes for {cls.__name__}: {exc}." # type: ignore
333332
)
334333
return data
335334

@@ -384,12 +383,11 @@ def publication_identifiers_validator(cls, value: Any) -> list[PublicationIdenti
384383
return list(value) # Re-cast into proper list-like type
385384

386385
# These 'synthetic' fields are generated from other model properties. Transform data from other properties as needed, setting
387-
# the appropriate field on the model itself. Then, proceed with Pydantic ingestion once fields are created.
386+
# the appropriate field on the model itself. Then, proceed with Pydantic ingestion once fields are created. Only perform these
387+
# transformations if the relevant attributes are present on the input data (i.e., when creating from an ORM object).
388388
@model_validator(mode="before")
389389
def generate_primary_and_secondary_publications(cls, data: Any):
390-
if not hasattr(data, "primary_publication_identifiers") or not hasattr(
391-
data, "secondary_publication_identifiers"
392-
):
390+
if hasattr(data, "publication_identifier_associations"):
393391
try:
394392
publication_identifiers = transform_record_publication_identifiers(
395393
data.publication_identifier_associations
@@ -400,33 +398,35 @@ def generate_primary_and_secondary_publications(cls, data: Any):
400398
data.__setattr__(
401399
"secondary_publication_identifiers", publication_identifiers["secondary_publication_identifiers"]
402400
)
403-
except AttributeError as exc:
404-
raise ValidationError(
405-
f"Unable to create {cls.__name__} without attribute: {exc}." # type: ignore
406-
)
401+
except (AttributeError, KeyError) as exc:
402+
raise ValidationError(f"Unable to coerce publication identifier attributes for {cls.__name__}: {exc}.")
407403
return data
408404

409405
@model_validator(mode="before")
410406
def transform_meta_analysis_objects_to_urns(cls, data: Any):
411-
if not hasattr(data, "meta_analyzes_score_set_urns"):
407+
if hasattr(data, "meta_analyzes_score_sets"):
412408
try:
413409
data.__setattr__(
414410
"meta_analyzes_score_set_urns", transform_score_set_list_to_urn_list(data.meta_analyzes_score_sets)
415411
)
416-
except AttributeError as exc:
417-
raise ValidationError(f"Unable to create {cls.__name__} without attribute: {exc}.") # type: ignore
412+
except (AttributeError, KeyError) as exc:
413+
raise ValidationError(
414+
f"Unable to coerce meta analyzes score set urn attribute for {cls.__name__}: {exc}."
415+
)
418416
return data
419417

420418
@model_validator(mode="before")
421419
def transform_meta_analyzed_objects_to_urns(cls, data: Any):
422-
if not hasattr(data, "meta_analyzed_by_score_set_urns"):
420+
if hasattr(data, "meta_analyzed_by_score_sets"):
423421
try:
424422
data.__setattr__(
425423
"meta_analyzed_by_score_set_urns",
426424
transform_score_set_list_to_urn_list(data.meta_analyzed_by_score_sets),
427425
)
428-
except AttributeError as exc:
429-
raise ValidationError(f"Unable to create {cls.__name__} without attribute: {exc}.") # type: ignore
426+
except (AttributeError, KeyError) as exc:
427+
raise ValidationError(
428+
f"Unable to coerce meta analyzed by score set urn attribute for {cls.__name__}: {exc}."
429+
)
430430
return data
431431

432432

tests/view_models/test_score_set.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
import pytest
44

55
from mavedb.view_models.publication_identifier import PublicationIdentifier, PublicationIdentifierCreate
6-
from mavedb.view_models.score_set import SavedScoreSet, ScoreSetCreate, ScoreSetModify, ScoreSetUpdateAllOptional
6+
from mavedb.view_models.score_set import (
7+
SavedScoreSet,
8+
ScoreSet,
9+
ScoreSetCreate,
10+
ScoreSetModify,
11+
ScoreSetUpdateAllOptional,
12+
)
713
from mavedb.view_models.target_gene import SavedTargetGene, TargetGeneCreate
814
from tests.helpers.constants import (
915
EXTRA_LICENSE,
@@ -17,6 +23,7 @@
1723
TEST_MINIMAL_SEQ_SCORESET_RESPONSE,
1824
TEST_PATHOGENICITY_SCORE_CALIBRATION,
1925
TEST_PUBMED_IDENTIFIER,
26+
VALID_EXPERIMENT_SET_URN,
2027
VALID_EXPERIMENT_URN,
2128
VALID_SCORE_SET_URN,
2229
VALID_TMP_URN,
@@ -372,10 +379,14 @@ def test_score_set_update_all_optional(attribute, updated_data):
372379

373380

374381
@pytest.mark.parametrize(
375-
"exclude",
376-
["publication_identifier_associations", "meta_analyzes_score_sets", "meta_analyzed_by_score_sets"],
382+
"exclude,expected_missing_fields",
383+
[
384+
("publication_identifier_associations", ["primaryPublicationIdentifiers", "secondaryPublicationIdentifiers"]),
385+
("meta_analyzes_score_sets", ["metaAnalyzesScoreSetUrns"]),
386+
("meta_analyzed_by_score_sets", ["metaAnalyzedByScoreSetUrns"]),
387+
],
377388
)
378-
def test_cannot_create_saved_score_set_without_all_attributed_properties(exclude):
389+
def test_cannot_create_saved_score_set_without_all_attributed_properties(exclude, expected_missing_fields):
379390
score_set = TEST_MINIMAL_SEQ_SCORESET_RESPONSE.copy()
380391
score_set["urn"] = "urn:score-set-xxx"
381392

@@ -429,8 +440,9 @@ def test_cannot_create_saved_score_set_without_all_attributed_properties(exclude
429440
with pytest.raises(ValueError) as exc_info:
430441
SavedScoreSet.model_validate(score_set_attributed_object)
431442

432-
assert "Unable to create SavedScoreSet without attribute" in str(exc_info.value)
433-
assert exclude in str(exc_info.value)
443+
assert "Field required" in str(exc_info.value)
444+
for exclude_field in expected_missing_fields:
445+
assert exclude_field in str(exc_info.value)
434446

435447

436448
def test_can_create_score_set_with_none_type_superseded_score_set_urn():
@@ -543,3 +555,14 @@ def test_cant_create_score_set_without_experiment_urn_if_not_meta_analysis():
543555
ScoreSetCreate(**score_set_test)
544556

545557
assert "experiment URN is required unless your score set is a meta-analysis" in str(exc_info.value)
558+
559+
560+
def test_can_create_score_set_from_non_orm_context():
561+
score_set_test = TEST_MINIMAL_SEQ_SCORESET_RESPONSE.copy()
562+
score_set_test["urn"] = "urn:score-set-xxx"
563+
score_set_test["experiment"]["urn"] = VALID_EXPERIMENT_URN
564+
score_set_test["experiment"]["experimentSetUrn"] = VALID_EXPERIMENT_SET_URN
565+
566+
saved_score_set = ScoreSet.model_validate(score_set_test)
567+
568+
assert saved_score_set.urn == "urn:score-set-xxx"

0 commit comments

Comments
 (0)