Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NRL-786 new parent class that does not allow extra fields for pydantic models #781

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,22 @@ generate-models: check-warn ## Generate Pydantic Models
--input ./api/producer/swagger.yaml \
--input-file-type openapi \
--output ./layer/nrlf/producer/fhir/r4/model.py \
--output-model-type "pydantic_v2.BaseModel"
--output-model-type "pydantic_v2.BaseModel" \
--base-class nrlf.core.parent_model.Parent
poetry run datamodel-codegen \
--strict-types {str,bytes,int,float,bool} \
--input ./api/producer/swagger.yaml \
--input-file-type openapi \
--output ./layer/nrlf/producer/fhir/r4/strict_model.py \
--base-class nrlf.core.parent_model.Parent \
--output-model-type "pydantic_v2.BaseModel"


@echo "Generating consumer model"
mkdir -p ./layer/nrlf/consumer/fhir/r4
poetry run datamodel-codegen \
--input ./api/consumer/swagger.yaml \
--input-file-type openapi \
--output ./layer/nrlf/consumer/fhir/r4/model.py \
--base-class nrlf.core.parent_model.Parent \
--output-model-type "pydantic_v2.BaseModel"
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from moto import mock_aws

from api.consumer.searchDocumentReference.search_document_reference import handler
from nrlf.consumer.fhir.r4.model import CodeableConcept, Identifier
from nrlf.core.constants import (
CATEGORY_ATTRIBUTES,
TYPE_ATTRIBUTES,
Expand Down Expand Up @@ -66,7 +67,9 @@ def test_search_document_reference_accession_number_in_pointer(
):
doc_ref = load_document_reference("Y05868-736253002-Valid")
doc_ref.identifier = [
{"type": {"text": "Accession-Number"}, "value": "Y05868.123456789"}
Identifier(
type=CodeableConcept(text="Accession-Number"), value="Y05868.123456789"
)
]
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
repository.create(doc_pointer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,6 @@ def test_update_document_reference_immutable_fields(repository):
)
],
text=None,
extension=None,
)

event = create_test_api_gateway_event(
Expand Down
60 changes: 31 additions & 29 deletions layer/nrlf/consumer/fhir/r4/model.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# generated by datamodel-codegen:
# filename: swagger.yaml
# timestamp: 2025-01-27T09:26:33+00:00
# timestamp: 2025-02-07T14:10:39+00:00

from __future__ import annotations

from typing import Annotated, List, Literal, Optional

from pydantic import BaseModel, Field, RootModel
from pydantic import Field, RootModel

from nrlf.core.parent_model import Parent


class LocationItem(RootModel[str]):
Expand All @@ -29,7 +31,7 @@ class ExpressionItem(RootModel[str]):
]


class BundleEntryRequest(BaseModel):
class BundleEntryRequest(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -81,7 +83,7 @@ class BundleEntryRequest(BaseModel):
] = None


class BundleEntrySearch(BaseModel):
class BundleEntrySearch(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -104,7 +106,7 @@ class BundleEntrySearch(BaseModel):
] = None


class BundleLink(BaseModel):
class BundleLink(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -124,7 +126,7 @@ class BundleLink(BaseModel):
]


class Attachment(BaseModel):
class Attachment(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -186,7 +188,7 @@ class Attachment(BaseModel):
] = None


class Coding(BaseModel):
class Coding(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -253,7 +255,7 @@ class NRLFormatCode(Coding):
]


class Period(BaseModel):
class Period(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -277,7 +279,7 @@ class Period(BaseModel):
] = None


class Quantity(BaseModel):
class Quantity(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -331,7 +333,7 @@ class ProfileItem(RootModel[str]):
]


class Meta(BaseModel):
class Meta(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -365,7 +367,7 @@ class Meta(BaseModel):
tag: Optional[List[Coding]] = None


class Narrative(BaseModel):
class Narrative(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -392,7 +394,7 @@ class DocumentId(RootModel[str]):
root: Annotated[str, Field(pattern="[A-Za-z0-9\\-\\.]{1,64}")]


class RequestPathParams(BaseModel):
class RequestPathParams(Parent):
id: DocumentId


Expand Down Expand Up @@ -450,7 +452,7 @@ class RequestHeaderCorrelationId(RootModel[str]):
root: Annotated[str, Field(examples=["11C46F5F-CDEF-4865-94B2-0EE0EDCC26DA"])]


class CodeableConcept(BaseModel):
class CodeableConcept(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -468,7 +470,7 @@ class CodeableConcept(BaseModel):
] = None


class Extension(BaseModel):
class Extension(Parent):
valueCodeableConcept: Annotated[
Optional[CodeableConcept],
Field(
Expand All @@ -487,11 +489,11 @@ class ContentStabilityExtensionValueCodeableConcept(CodeableConcept):
]


class RequestHeader(BaseModel):
class RequestHeader(Parent):
odsCode: RequestHeaderOdsCode


class RequestParams(BaseModel):
class RequestParams(Parent):
subject_identifier: Annotated[
RequestQuerySubject, Field(alias="subject:identifier")
]
Expand All @@ -505,13 +507,13 @@ class RequestParams(BaseModel):
] = None


class CountRequestParams(BaseModel):
class CountRequestParams(Parent):
subject_identifier: Annotated[
RequestQuerySubject, Field(alias="subject:identifier")
]


class OperationOutcomeIssue(BaseModel):
class OperationOutcomeIssue(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -557,7 +559,7 @@ class ContentStabilityExtension(Extension):
valueCodeableConcept: ContentStabilityExtensionValueCodeableConcept


class OperationOutcome(BaseModel):
class OperationOutcome(Parent):
resourceType: Literal["OperationOutcome"]
id: Annotated[
Optional[str],
Expand Down Expand Up @@ -595,7 +597,7 @@ class OperationOutcome(BaseModel):
issue: Annotated[List[OperationOutcomeIssue], Field(min_length=1)]


class DocumentReferenceContent(BaseModel):
class DocumentReferenceContent(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -620,7 +622,7 @@ class DocumentReferenceContent(BaseModel):
]


class DocumentReference(BaseModel):
class DocumentReference(Parent):
resourceType: Literal["DocumentReference"]
id: Annotated[
Optional[str],
Expand Down Expand Up @@ -721,7 +723,7 @@ class DocumentReference(BaseModel):
] = None


class Bundle(BaseModel):
class Bundle(Parent):
resourceType: Literal["Bundle"]
id: Annotated[
Optional[str],
Expand Down Expand Up @@ -786,7 +788,7 @@ class Bundle(BaseModel):
] = None


class BundleEntry(BaseModel):
class BundleEntry(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -828,7 +830,7 @@ class BundleEntry(BaseModel):
] = None


class BundleEntryResponse(BaseModel):
class BundleEntryResponse(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -872,7 +874,7 @@ class BundleEntryResponse(BaseModel):
] = None


class DocumentReferenceContext(BaseModel):
class DocumentReferenceContext(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -907,7 +909,7 @@ class DocumentReferenceContext(BaseModel):
related: Optional[List[Reference]] = None


class DocumentReferenceRelatesTo(BaseModel):
class DocumentReferenceRelatesTo(Parent):
id: Annotated[
Optional[str],
Field(
Expand All @@ -927,7 +929,7 @@ class DocumentReferenceRelatesTo(BaseModel):
]


class Identifier(BaseModel):
class Identifier(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -972,7 +974,7 @@ class Identifier(BaseModel):
] = None


class Reference(BaseModel):
class Reference(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down Expand Up @@ -1009,7 +1011,7 @@ class Reference(BaseModel):
] = None


class Signature(BaseModel):
class Signature(Parent):
id: Annotated[
Optional[str],
Field(
Expand Down
88 changes: 88 additions & 0 deletions layer/nrlf/core/parent_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from typing import Annotated, List, Optional

from pydantic import BaseModel, ConfigDict, Field


class ParentCoding(BaseModel):
model_config = ConfigDict(regex_engine="python-re", extra="forbid")
id: Annotated[
Optional[str],
Field(
description="Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
pattern="[A-Za-z0-9\\-\\.]{1,64}",
),
] = None
system: Annotated[
Optional[str],
Field(
description="The identification of the code system that defines the meaning of the symbol in the code.",
pattern="\\S*",
),
] = None
version: Annotated[
Optional[str],
Field(
description="The version of the code system which was used when choosing this code. Note that a well–maintained code system does not need the version reported, because the meaning of codes is consistent across versions. However this cannot consistently be assured, and when the meaning is not guaranteed to be consistent, the version SHOULD be exchanged.",
pattern="[ \\r\\n\\t\\S]+",
),
] = None
code: Annotated[
Optional[str],
Field(
description="A symbol in syntax defined by the system. The symbol may be a predefined code or an expression in a syntax defined by the coding system (e.g. post–coordination).",
pattern="[^\\s]+(\\s[^\\s]+)*",
),
] = None
display: Annotated[
Optional[str],
Field(
description="A representation of the meaning of the code in the system, following the rules of the system.",
pattern="[ \\r\\n\\t\\S]+",
),
] = None
userSelected: Annotated[
Optional[bool],
Field(
description="Indicates that this coding was chosen by a user directly – e.g. off a pick list of available items (codes or displays)."
),
] = None


class ParentCodeableConcept(BaseModel):
model_config = ConfigDict(regex_engine="python-re", extra="forbid")
id: Annotated[
Optional[str],
Field(
description="Unique id for the element within a resource (for internal references). This may be any string value that does not contain spaces.",
pattern="[A-Za-z0-9\\-\\.]{1,64}",
),
] = None
coding: Optional[List[ParentCoding]] = None
text: Annotated[
Optional[str],
Field(
description="A human language representation of the concept as seen/selected/uttered by the user who entered the data and/or which represents the intended meaning of the user.",
pattern="[ \\r\\n\\t\\S]+",
),
] = None


class ParentExtension(BaseModel):
valueCodeableConcept: Annotated[
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to allow producers to define their own extensions, do we need value to be more generic to support extensions with values of types other than CodableConcepts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I've just kept the extension as its always been defined but could make it more generic ?

Optional[ParentCodeableConcept],
Field(
description="A name which details the functional use for this link – see [http://www.iana.org/assignments/link–relations/link–relations.xhtml#link–relations–1](http://www.iana.org/assignments/link–relations/link–relations.xhtml#link–relations–1)."
),
] = None
url: Annotated[
Optional[str],
Field(description="The reference details for the link.", pattern="\\S*"),
] = None


class Parent(BaseModel):
model_config = ConfigDict(regex_engine="python-re", extra="forbid")
extension: Annotated[
Optional[List[ParentExtension]],
Field(description="A list of relevant extensions"),
] = None
Loading
Loading