1
1
import ast
2
- import functools
3
2
import itertools
4
3
import keyword
5
4
import logging
6
5
from collections .abc import Iterable
7
6
from dataclasses import replace
8
7
from enum import Enum , auto
9
- from typing import Literal , cast
8
+ from typing import Literal
10
9
11
10
from fhir_py_types import (
12
11
StructureDefinition ,
@@ -79,11 +78,13 @@ def format_identifier(
79
78
def uppercamelcase (s : str ) -> str :
80
79
return s [:1 ].upper () + s [1 :]
81
80
82
- return (
83
- identifier + uppercamelcase (type_ .code )
84
- if is_polymorphic (definition )
85
- else identifier
86
- )
81
+ if is_polymorphic (definition ):
82
+ # TODO: it's fast hack
83
+ if type_ .code [0 ].islower ():
84
+ return identifier + uppercamelcase (clear_primitive_id (type_ .code ))
85
+ return identifier + uppercamelcase (type_ .code )
86
+
87
+ return identifier
87
88
88
89
89
90
def remap_type (
@@ -94,8 +95,8 @@ def remap_type(
94
95
case "Resource" :
95
96
# Different contexts use 'Resource' type to refer to any
96
97
# resource differentiated by its 'resourceType' (tagged union).
97
- # 'AnyResource' is not defined by the spec but rather
98
- # generated as a union of all defined resource types.
98
+ # 'AnyResource' is defined in header as a special type
99
+ # that dynamically replaced with a right type in run-time
99
100
type_ = replace (type_ , code = "AnyResource" )
100
101
101
102
if is_polymorphic (definition ):
@@ -106,6 +107,12 @@ def remap_type(
106
107
# with a custom validator that will enforce single required property rule.
107
108
type_ = replace (type_ , required = False )
108
109
110
+ if is_primitive_type (type_ ):
111
+ # Primitive types defined from the small letter (like code)
112
+ # and it might overlap with model fields
113
+ # e.g. QuestionnaireItem has attribute code and linkId has type code
114
+ type_ = replace (type_ , code = make_primitive_id (type_ .code ))
115
+
109
116
return type_
110
117
111
118
@@ -115,8 +122,7 @@ def zip_identifier_type(
115
122
result = []
116
123
117
124
for t in [remap_type (definition , t ) for t in definition .type ]:
118
- name = format_identifier (definition , identifier , t )
119
- result .append ((name , t ))
125
+ result .append ((format_identifier (definition , identifier , t ), t ))
120
126
if definition .kind != StructureDefinitionKind .PRIMITIVE and is_primitive_type (
121
127
t
122
128
):
@@ -185,10 +191,16 @@ def order_type_overriding_properties(
185
191
def define_class_object (
186
192
definition : StructureDefinition ,
187
193
) -> Iterable [ast .stmt | ast .expr ]:
194
+ bases : list [ast .expr ] = []
195
+ if definition .kind == StructureDefinitionKind .RESOURCE :
196
+ bases .append (ast .Name ("AnyResource" ))
197
+ # BaseModel should be the last, because it overrides `extra`
198
+ bases .append (ast .Name ("BaseModel" ))
199
+
188
200
return [
189
201
ast .ClassDef (
190
202
definition .id ,
191
- bases = [ ast . Name ( "BaseModel" )] ,
203
+ bases = bases ,
192
204
body = [
193
205
ast .Expr (value = ast .Constant (definition .docstring )),
194
206
* itertools .chain .from_iterable (
@@ -202,11 +214,6 @@ def define_class_object(
202
214
keywords = [],
203
215
type_params = [],
204
216
),
205
- ast .Call (
206
- ast .Attribute (value = ast .Name (definition .id ), attr = "update_forward_refs" ),
207
- args = [],
208
- keywords = [],
209
- ),
210
217
]
211
218
212
219
@@ -215,48 +222,22 @@ def define_class(definition: StructureDefinition) -> Iterable[ast.stmt | ast.exp
215
222
216
223
217
224
def define_alias (definition : StructureDefinition ) -> Iterable [ast .stmt ]:
218
- return type_annotate (definition , definition .id , AnnotationForm .TypeAlias )
219
-
220
-
221
- def define_tagged_union (
222
- name : str , components : Iterable [StructureDefinition ], distinct_by : str
223
- ) -> ast .stmt :
224
- annotation = functools .reduce (
225
- lambda acc , n : ast .BinOp (left = acc , right = n , op = ast .BitOr ()),
226
- (cast (ast .expr , ast .Name (d .id )) for d in components ),
225
+ # Primitive types are renamed to another name to avoid overlapping with model fields
226
+ return type_annotate (
227
+ definition , make_primitive_id (definition .id ), AnnotationForm .TypeAlias
227
228
)
228
229
229
- return ast .Assign (
230
- targets = [ast .Name (name )],
231
- value = ast .Subscript (
232
- value = ast .Name ("Annotated_" ),
233
- slice = ast .Tuple (
234
- elts = [
235
- annotation ,
236
- ast .Call (
237
- ast .Name ("Field" ),
238
- args = [ast .Constant (...)],
239
- keywords = [
240
- ast .keyword (
241
- arg = "discriminator" , value = ast .Constant (distinct_by )
242
- ),
243
- ],
244
- ),
245
- ]
246
- ),
247
- ),
248
- )
249
230
231
+ def make_primitive_id (name : str ) -> str :
232
+ if name in ("str" , "int" , "float" , "bool" ):
233
+ return name
234
+ return f"{ name } Type"
250
235
251
- def select_tagged_resources (
252
- definitions : Iterable [StructureDefinition ], key : str
253
- ) -> Iterable [StructureDefinition ]:
254
- return (
255
- definition
256
- for definition in definitions
257
- if definition .kind == StructureDefinitionKind .RESOURCE
258
- and key in definition .elements
259
- )
236
+
237
+ def clear_primitive_id (name : str ) -> str :
238
+ if name .endswith ("Type" ):
239
+ return name [:- 4 ]
240
+ return name
260
241
261
242
262
243
def select_nested_definitions (
@@ -302,14 +283,6 @@ def build_ast(
302
283
f"Unsupported definition { definition .id } of kind { definition .kind } , skipping"
303
284
)
304
285
305
- resources = list (select_tagged_resources (structure_definitions , key = "resourceType" ))
306
- if resources :
307
- typedefinitions .append (
308
- define_tagged_union (
309
- name = "AnyResource" , components = resources , distinct_by = "resourceType"
310
- )
311
- )
312
-
313
286
return sorted (
314
287
typedefinitions ,
315
288
# Defer any postprocessing until after the structure tree is defined.
0 commit comments