From 6c906d1511bf8b1ac9a32ebe78b56739bc532bf9 Mon Sep 17 00:00:00 2001 From: Axel Vanraes Date: Fri, 2 Jun 2023 23:10:11 +0200 Subject: [PATCH 1/5] Remove Optional typing for arrays. Added default factory for optional arrays. --- fhir_py_types/ast.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/fhir_py_types/ast.py b/fhir_py_types/ast.py index 7a3bd4e..465b422 100644 --- a/fhir_py_types/ast.py +++ b/fhir_py_types/ast.py @@ -40,33 +40,37 @@ def make_type_annotation( if type_.isarray: annotation = ast.Subscript(value=ast.Name("List_"), slice=annotation) - if not type_.required and form != AnnotationForm.TypeAlias: + if not type_.required and form != AnnotationForm.TypeAlias and not type_.isarray: annotation = ast.Subscript(value=ast.Name("Optional_"), slice=annotation) return annotation def make_default_initializer(identifier: str, type_: StructurePropertyType): - default: ast.expr | None = None - + default_value = ast.Constant(None) + keywords: List[ast.keyword] = [] + if type_.isarray: + if not type_.required: + keywords.append(ast.keyword(arg="default", value=ast.Ellipsis())) + else: + keywords.append(ast.keyword(arg="default_factory", value=ast.Name("list"))) + else: + if type_.required: + default_value = ast.Ellipsis() + elif type_.literal: + default_value = ast.Str(type_.code) + else: + default_value = ast.Constant(None) + keywords.append(ast.keyword(arg="default", value=default_value)) if keyword.iskeyword(identifier): - default = ast.Call( + keywords.append( + ast.keyword(arg="alias", value=ast.Constant(identifier)), + ) + default = ast.Call( ast.Name("Field"), args=[], - keywords=[ - *( - [ast.keyword(arg="default", value=ast.Constant(None))] - if not type_.required - else [] - ), - ast.keyword(arg="alias", value=ast.Constant(identifier)), - ], + keywords=keywords, ) - else: - if not type_.required: - default = ast.Constant(None) - elif type_.literal and not type_.isarray: - default = ast.Str(type_.code) return default From 55907dc998287fc029fd8e8b7a129b68f5fe9c5c Mon Sep 17 00:00:00 2001 From: Axel Vanraes Date: Sat, 3 Jun 2023 00:32:24 +0200 Subject: [PATCH 2/5] fix empty list bug --- fhir_py_types/ast.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fhir_py_types/ast.py b/fhir_py_types/ast.py index 465b422..65ffd6c 100644 --- a/fhir_py_types/ast.py +++ b/fhir_py_types/ast.py @@ -49,11 +49,8 @@ def make_type_annotation( def make_default_initializer(identifier: str, type_: StructurePropertyType): default_value = ast.Constant(None) keywords: List[ast.keyword] = [] - if type_.isarray: - if not type_.required: - keywords.append(ast.keyword(arg="default", value=ast.Ellipsis())) - else: - keywords.append(ast.keyword(arg="default_factory", value=ast.Name("list"))) + if type_.isarray and not type_.required: + keywords.append(ast.keyword(arg="default_factory", value=ast.Name("list"))) else: if type_.required: default_value = ast.Ellipsis() From 7aed286c33832126cb91fb339ba1a4e5f05393a3 Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sun, 15 Dec 2024 17:19:01 +0100 Subject: [PATCH 3/5] Add model_dump_json and set all exclude_ to true --- fhir_py_types/header.py.tpl | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/fhir_py_types/header.py.tpl b/fhir_py_types/header.py.tpl index b32a2b9..c89e2bf 100644 --- a/fhir_py_types/header.py.tpl +++ b/fhir_py_types/header.py.tpl @@ -47,14 +47,14 @@ class BaseModel(BaseModel_): exclude: IncEx = None, context: Any_ | None = None, by_alias: bool = True, - exclude_unset: bool = False, - exclude_defaults: bool = False, + exclude_unset: bool = True, + exclude_defaults: bool = True, exclude_none: bool = True, round_trip: bool = False, warnings: bool | Literal_["none", "warn", "error"] = True, serialize_as_any: bool = False, ): - # Override default parameters for by_alias and exclude_none preserving function declaration + # Override default parameters for by_alias and exclude_* preserving function declaration return super().model_dump( mode=mode, include=include, @@ -69,6 +69,36 @@ class BaseModel(BaseModel_): serialize_as_any=serialize_as_any, ) + def model_dump_json( + self, + *, + indent: int | None = None, + include: IncEx | None = None, + exclude: IncEx | None = None, + context: Any_ | None = None, + by_alias: bool = True, + exclude_unset: bool = True, + exclude_defaults: bool = True, + exclude_none: bool = True, + round_trip: bool = False, + warnings: bool | Literal_['none', 'warn', 'error'] = True, + serialize_as_any: bool = False, + ) -> str: + # Override default parameters for by_alias and exclude_* preserving function declaration + return super().model_dump_json( + indent=indent, + include=include, + exclude=exclude, + context=context, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + round_trip=round_trip, + warnings=warnings, + serialize_as_any=serialize_as_any, + ) + @field_serializer("*") @classmethod def serialize_all_fields(cls, value: Any_, info: SerializationInfo): From 54597ac7d392b61c377d75451d2c31b95904b66e Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sun, 15 Dec 2024 17:19:23 +0100 Subject: [PATCH 4/5] Restore the previous behaviour for non-aray and required fields --- fhir_py_types/ast.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fhir_py_types/ast.py b/fhir_py_types/ast.py index 4bb7a93..34bad3b 100644 --- a/fhir_py_types/ast.py +++ b/fhir_py_types/ast.py @@ -54,13 +54,10 @@ def make_default_initializer( if type_.isarray and not type_.required: keywords.append(ast.keyword(arg="default_factory", value=ast.Name("list"))) else: - if type_.required: - default_value = ast.Constant(...) + if not type_.required: + keywords.append(ast.keyword(arg="default", value=ast.Constant(None))) elif type_.literal: - default_value = ast.Constant(type_.code) - else: - default_value = ast.Constant(None) - keywords.append(ast.keyword(arg="default", value=default_value)) + keywords.append(ast.keyword(arg="default", value=ast.Constant(type_.code))) if keyword.iskeyword(identifier) or type_.alias: keywords.append( ast.keyword(arg="alias", value=ast.Constant(type_.alias or identifier)), From 6405f6471fc327059646901483887abd896d286e Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Sun, 15 Dec 2024 18:02:45 +0100 Subject: [PATCH 5/5] Update tests --- tests/test_ast.py | 189 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 152 insertions(+), 37 deletions(-) diff --git a/tests/test_ast.py b/tests/test_ast.py index a1dc78e..f71f8de 100644 --- a/tests/test_ast.py +++ b/tests/test_ast.py @@ -14,23 +14,12 @@ def assert_eq( definitions: Sequence[StructureDefinition], ast_tree: Sequence[ast.stmt | ast.expr] ) -> None: - generated = [ast.dump(t) for t in build_ast(definitions)] - expected = [ast.dump(t) for t in ast_tree] + generated = [ast.dump(t, indent=1) for t in build_ast(definitions)] + expected = [ast.dump(t, indent=1) for t in ast_tree] assert generated == expected -def build_field_with_alias(identifier: str) -> ast.Call: - return ast.Call( - func=ast.Name(id="Field"), - args=[], - keywords=[ - ast.keyword(arg="default", value=ast.Constant(value=None)), - ast.keyword(arg="alias", value=ast.Constant(value=identifier)), - ], - ) - - def test_generates_empty_ast_from_empty_definitions() -> None: assert build_ast([]) == [] @@ -64,13 +53,14 @@ def test_generates_class_for_flat_definition() -> None: [ ast.ClassDef( name="TestResource", - bases=[ast.Name(id='AnyResource'), ast.Name(id="BaseModel")], + bases=[ast.Name(id="AnyResource"), ast.Name(id="BaseModel")], keywords=[], body=[ ast.Expr(value=ast.Constant(value="test resource description")), ast.AnnAssign( target=ast.Name(id="property1"), annotation=ast.Constant("str"), + value=ast.Call(func=ast.Name(id="Field"), args=[], keywords=[]), simple=1, ), ast.Expr(value=ast.Constant(value="test resource property 1")), @@ -80,7 +70,18 @@ def test_generates_class_for_flat_definition() -> None: value=ast.Name(id="Optional_"), slice=ast.Constant("Element"), ), - value=build_field_with_alias("_property1"), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ), + ast.keyword( + arg="alias", value=ast.Constant(value="_property1") + ), + ], + ), simple=1, ), ast.Expr(value=ast.Constant(value="test resource property 1")), @@ -174,7 +175,15 @@ def test_generates_multiple_classes_for_compound_definition() -> None: value=ast.Name(id="Optional_"), slice=ast.Constant("str") ), simple=1, - value=ast.Constant(None), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ) + ], + ), ), ast.Expr( value=ast.Constant(value="nested test resource property 1") @@ -186,7 +195,19 @@ def test_generates_multiple_classes_for_compound_definition() -> None: slice=ast.Constant("Element"), ), simple=1, - value=build_field_with_alias("_property1"), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", + value=ast.Constant(value=None), + ), + ast.keyword( + arg="alias", value=ast.Constant(value="_property1") + ), + ], + ), ), ast.Expr( value=ast.Constant(value="nested test resource property 1") @@ -205,6 +226,7 @@ def test_generates_multiple_classes_for_compound_definition() -> None: target=ast.Name(id="complexproperty"), annotation=ast.Constant("NestedTestResource"), simple=1, + value=ast.Call(func=ast.Name(id="Field"), args=[], keywords=[]), ), ast.Expr(value=ast.Constant(value="nested complex definition")), ], @@ -235,10 +257,8 @@ def test_generates_multiple_classes_for_compound_definition() -> None: True, False, ast.Subscript( - value=ast.Name(id="Optional_"), - slice=ast.Subscript( - value=ast.Name(id="List_"), slice=ast.Constant("str") - ), + value=ast.Name(id="List_"), + slice=ast.Constant("str"), ), ), ( @@ -303,7 +323,7 @@ def test_generates_annotations_according_to_structure_type( [ ast.ClassDef( name="TestResource", - bases=[ast.Name(id='AnyResource'), ast.Name(id="BaseModel")], + bases=[ast.Name(id="AnyResource"), ast.Name(id="BaseModel")], keywords=[], body=[ ast.Expr(value=ast.Constant(value="test resource description")), @@ -311,21 +331,46 @@ def test_generates_annotations_according_to_structure_type( target=ast.Name(id="property1"), annotation=expected_annotation, simple=1, - value=ast.Constant(None) + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default_factory", value=ast.Name(id="list") + ), + ], + ) + if isarray and not required + else ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ), + ], + ) if not required - else ast.Constant("str") + else ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword(arg="default", value=ast.Constant("str")), + ], + ) if literal - else None, + else ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[], + ), ), ast.Expr(value=ast.Constant(value="test resource property 1")), ast.AnnAssign( target=ast.Name(id="property1__ext"), annotation=ast.Subscript( - value=ast.Name(id="Optional_"), - slice=ast.Subscript( - value=ast.Name(id="List_"), - slice=ast.Constant("Element"), - ), + value=ast.Name(id="List_"), + slice=ast.Constant(value="Element"), ) if isarray else ast.Subscript( @@ -333,7 +378,30 @@ def test_generates_annotations_according_to_structure_type( slice=ast.Constant("Element"), ), simple=1, - value=build_field_with_alias("_property1"), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + *( + [ + ast.keyword( + arg="default_factory", + value=ast.Name(id="list"), + ) + ] + if isarray + else [ + ast.keyword( + arg="default", + value=ast.Constant(value=None), + ), + ] + ), + ast.keyword( + arg="alias", value=ast.Constant(value="_property1") + ), + ], + ), ), ast.Expr(value=ast.Constant(value="test resource property 1")), ], @@ -386,7 +454,7 @@ def test_unrolls_required_polymorphic_into_class_union() -> None: [ ast.ClassDef( name="TestResource", - bases=[ast.Name(id='AnyResource'),ast.Name(id="BaseModel")], + bases=[ast.Name(id="AnyResource"), ast.Name(id="BaseModel")], keywords=[], body=[ ast.Expr(value=ast.Constant(value="test resource description")), @@ -397,7 +465,15 @@ def test_unrolls_required_polymorphic_into_class_union() -> None: slice=ast.Constant("booleanType"), ), simple=1, - value=ast.Constant(None), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ) + ], + ), ), ast.Expr(value=ast.Constant(value="monotype property definition")), ast.AnnAssign( @@ -407,7 +483,18 @@ def test_unrolls_required_polymorphic_into_class_union() -> None: slice=ast.Constant("Element"), ), simple=1, - value=build_field_with_alias("_monotype"), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ), + ast.keyword( + arg="alias", value=ast.Constant(value="_monotype") + ), + ], + ), ), ast.Expr(value=ast.Constant(value="monotype property definition")), ast.AnnAssign( @@ -417,7 +504,15 @@ def test_unrolls_required_polymorphic_into_class_union() -> None: slice=ast.Constant("booleanType"), ), simple=1, - value=ast.Constant(None), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ), + ], + ), ), ast.Expr( value=ast.Constant(value="polymorphic property definition") @@ -429,7 +524,19 @@ def test_unrolls_required_polymorphic_into_class_union() -> None: slice=ast.Constant("Element"), ), simple=1, - value=build_field_with_alias("_valueBoolean"), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ), + ast.keyword( + arg="alias", + value=ast.Constant(value="_valueBoolean"), + ), + ], + ), ), ast.Expr( value=ast.Constant(value="polymorphic property definition") @@ -441,7 +548,15 @@ def test_unrolls_required_polymorphic_into_class_union() -> None: slice=ast.Constant("Quantity"), ), simple=1, - value=ast.Constant(None), + value=ast.Call( + func=ast.Name(id="Field"), + args=[], + keywords=[ + ast.keyword( + arg="default", value=ast.Constant(value=None) + ), + ], + ), ), ast.Expr( value=ast.Constant(value="polymorphic property definition")