Skip to content

Commit f022119

Browse files
committed
feat: python property and class generation
1 parent 179c25b commit f022119

File tree

2 files changed

+96
-64
lines changed

2 files changed

+96
-64
lines changed

src/aidbox_sdk/generator/python.clj

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77
(:import
88
[aidbox_sdk.generator CodeGenerator]))
99

10-
(defn fhir-type->lang-type [fhir-type]
10+
(defn ->lang-type [fhir-type]
1111
(case fhir-type
1212
;; Primitive Types
1313
"boolean" "bool"
1414
"instant" "str"
1515
"time" "str"
1616
"date" "str"
1717
"dateTime" "str"
18-
"decimal" "str"
18+
"decimal" "float"
1919

2020
"integer" "integer"
2121
"unsignedInt" "integer"
2222
"positiveInt" "integer"
2323

24-
"integer64" "str"
24+
"integer64" "integer"
2525
"base64Binary" "str"
2626

2727
"uri" "str"
@@ -38,13 +38,13 @@
3838
;; else
3939
fhir-type))
4040

41-
(defn url->resource-type [reference]
41+
(defn url->resource-name [reference]
4242
(last (str/split (str reference) #"/")))
4343

4444
(defn class-name
4545
"Generate class name from schema url."
4646
[url]
47-
(uppercase-first-letter (url->resource-type url)))
47+
(uppercase-first-letter (url->resource-name url)))
4848

4949
(defn generate-deps [deps]
5050
(->> deps
@@ -55,11 +55,12 @@
5555
(str/join "\n")))
5656

5757
(defn package->directory
58-
"Generate directory name from package name.
59-
hl7.fhir.r4.core#4.0.1 -> hl7-fhir-r4-core"
58+
"Generates directory name from package name.
59+
60+
Example:
61+
hl7.fhir.r4.core -> hl7-fhir-r4-core"
6062
[x]
61-
(-> x
62-
(str/replace #"[\.#]" "-")))
63+
(str/replace x #"[\.#]" "-"))
6364

6465
(defn resource-file-path [ir-schema]
6566
(io/file (package->directory (:package ir-schema))
@@ -69,49 +70,74 @@
6970
"")
7071

7172
(defn generate-property
72-
"Generate class property from schema element."
73+
"Generates class property from schema element."
7374
[element]
74-
(let [type (str
75-
()
76-
(fhir-type->lang-type
77-
(:original-type element))
78-
(when (:array element) "[]")
79-
(when (and (not (:required element))
80-
(not (:literal element))) "?"))
81-
name (->snake-case (:name element))]
75+
(let [name (->snake-case (:name element))
76+
lang-type (->lang-type (:type element))
77+
type (str
78+
(cond
79+
;; required and array
80+
(and (:required element)
81+
(:array element))
82+
(format "List[%s]" lang-type)
83+
84+
;; not required and array
85+
(and (not (:required element))
86+
(:array element))
87+
(format "Optional[List[%s]]" lang-type)
88+
89+
;; required and not array
90+
(and (:required element)
91+
(not (:array element)))
92+
lang-type
93+
94+
;; not required and not array
95+
(and (not (:required element))
96+
(not (:array element)))
97+
(format "Optional[%s]" lang-type)))
98+
99+
default-value (cond
100+
(not (:required element))
101+
"None"
102+
103+
(and (:required element)
104+
(:array element))
105+
"[]"
106+
107+
:else nil)]
108+
82109
(if (contains? element :choices)
83110
(generate-polymorphic-property element)
84-
(str name ": " type (when-not (:required element) " = None")))))
85-
86-
(defn generate-class [schema & [inner-classes]]
87-
(let [base-class (url->resource-type (:base schema))
88-
schema-name (or (:url schema) (:name schema))
89-
generic (when (= (:type schema) "Bundle") "<T>")
111+
(str name ": " type (when default-value (str " = " default-value))))))
112+
113+
(defn generate-class
114+
"Generates Python class from IR (intermediate representation) schema."
115+
[ir-schema & [inner-classes]]
116+
(let [base-class (url->resource-name (:base ir-schema))
117+
schema-name (or (:url ir-schema) (:name ir-schema))
118+
generic (when (= (:type ir-schema) "Bundle") "<T>")
90119
class-name' (class-name (str schema-name generic))
91-
elements (->> (:elements schema)
120+
elements (->> (:elements ir-schema)
92121
(map #(if (and (= (:base %) "Bundle_Entry")
93122
(= (:name %) "resource"))
94123
(assoc % :value "T")
95124
%)))
96125
properties (->> elements
126+
(sort-by :name)
97127
(map generate-property)
98128
(map u/add-indent)
99129
(str/join "\n"))
100-
base-class (cond
101-
(= base-class "DomainResource") "DomainResource, IResource"
102-
:else base-class)
103130
base-class-name (when-not (str/blank? base-class)
104131
(uppercase-first-letter base-class))]
105-
106-
(str "class " class-name' "(" base-class-name "):"
107-
(when-not (str/blank? properties)
108-
"\n")
109-
properties
110-
(when (and inner-classes
111-
(seq inner-classes))
112-
"\n\n")
113-
(str/join "\n\n" (map #(->> % str/split-lines (map u/add-indent) (str/join "\n")) inner-classes))
114-
"\n}")))
132+
(str
133+
(str/join "\n\n" (map #(->> % str/split-lines (map u/add-indent) (str/join "\n")) inner-classes))
134+
"class " class-name' "(" base-class-name "):"
135+
(when-not (str/blank? properties)
136+
"\n")
137+
properties
138+
(when (and inner-classes
139+
(seq inner-classes))
140+
"\n\n"))))
115141

116142
(defn generate-module
117143
[& {:keys [deps classes]
@@ -122,7 +148,9 @@
122148
(flatten)
123149
(str/join "\n")))
124150

125-
(defn generate-backbone-classes [ir-schema]
151+
(defn generate-backbone-classes
152+
"Generates classes from schema's backbone elements."
153+
[ir-schema]
126154
(->> (ir-schema :backbone-elements)
127155
(map #(assoc % :base "BackboneElement"))
128156
(map generate-class)))
@@ -146,7 +174,9 @@
146174
(generate-resource-module [_ ir-schema]
147175
{:path (resource-file-path ir-schema)
148176
:content (generate-module
149-
:deps [{:module "..base" :members ["*"]}]
177+
:deps [{:module "pydantic" :members ["*"]}
178+
{:module "typing" :members ["Optional" "List"]}
179+
{:module "..base" :members ["*"]}]
150180
:classes [(generate-class ir-schema
151181
(generate-backbone-classes ir-schema))])})
152182
(generate-search-params [_ search-schemas fhir-schemas])

test/aidbox_sdk/generator/python_test.clj

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,40 @@
2222

2323
(deftest test-generate-property
2424
(testing "simple case"
25-
(is (= "active Optional[bool] = None"
25+
(is (= "active: Optional[bool] = None"
2626
(gen.python/generate-property {:name "active",
2727
:base "Patient",
2828
:array false,
2929
:required false,
30-
:value "bool"}))))
30+
:value "bool"
31+
:type "boolean"}))))
3132

3233
(testing "required"
3334
(is (= "type: str"
3435
(gen.python/generate-property {:name "type",
3536
:base "Patient_Link",
3637
:array false,
3738
:required true,
38-
:value "string"}))))
39+
:value "string"
40+
:type "string"}))))
3941

4042
(testing "array optional"
4143
(is (= "address: Optional[List[Address]] = None"
4244
(gen.python/generate-property {:name "address",
4345
:base "Patient",
4446
:array true,
4547
:required false,
46-
:value "Address"}))))
48+
:value "Address"
49+
:type "Address"}))))
4750

4851
(testing "array required"
4952
(is (= "extension: list[Extension] = []"
5053
(gen.python/generate-property {:name "extension",
5154
:base "Element",
5255
:array true,
5356
:required true,
54-
:value "Extension"}))))
57+
:value "Extension"
58+
:type "Extension"}))))
5559

5660
(testing "element with literal"
5761
;; TODO
@@ -65,35 +69,33 @@
6569
;; TODO
6670
))
6771

68-
#_(deftest test-generate-class
72+
(deftest test-generate-class
73+
(testing "base"
74+
(is (= (gen.python/generate-class fixtures/patient-ir-schema)
75+
"class Patient(DomainResource):\n active: Optional[bool] = None\n address: Optional[List[Address]] = None\n birth_date: Optional[str] = None\n communication: Optional[List[BackboneElement]] = None\n contact: Optional[List[BackboneElement]] = None\n \n deceased_boolean: Optional[bool] = None\n deceased_date_time: Optional[str] = None\n gender: Optional[str] = None\n general_practitioner: Optional[List[Reference]] = None\n identifier: Optional[List[Identifier]] = None\n link: Optional[List[BackboneElement]] = None\n managing_organization: Optional[Reference] = None\n marital_status: Optional[CodeableConcept] = None\n \n multiple_birth_boolean: Optional[bool] = None\n multiple_birth_integer: Optional[integer] = None\n name: Optional[List[HumanName]] = None\n photo: Optional[List[Attachment]] = None\n telecom: Optional[List[ContactPoint]] = None"))))
6976

70-
(testing ""))
71-
72-
#_
73-
(deftest generate-datatypes
74-
(is
75-
(= (sut/generate-datatypes generator [fixtures/coding-ir-schema])
77+
#_(deftest generate-datatypes
78+
(is
79+
(= (sut/generate-datatypes generator [fixtures/coding-ir-schema])
7680

77-
[{:path (io/file "base" "__init__.py"),
78-
:content
79-
"namespace Aidbox.FHIR.Base;\n\npublic class Coding : Element\n{\n public string? Code { get; set; }\n public string? System { get; set; }\n public string? Display { get; set; }\n public string? Version { get; set; }\n public bool? UserSelected { get; set; }\n}"}])))
81+
[{:path (io/file "base" "__init__.py"),
82+
:content
83+
"namespace Aidbox.FHIR.Base;\n\npublic class Coding : Element\n{\n public string? Code { get; set; }\n public string? System { get; set; }\n public string? Display { get; set; }\n public string? Version { get; set; }\n public bool? UserSelected { get; set; }\n}"}])))
8084

8185
(deftest test-generate-resources
8286
(is
8387
(= (sut/generate-resource-module generator fixtures/patient-ir-schema)
84-
8588
{:path (io/file "hl7-fhir-r4-core/Patient.cs"),
8689
:content
8790
"using Aidbox.FHIR.Base;\nusing Aidbox.FHIR.Utils;\n\nnamespace Aidbox.FHIR.R4.Core;\n\npublic class Patient : DomainResource, IResource\n{\n public bool? MultipleBirthBoolean { get; set; }\n public Base.Address[]? Address { get; set; }\n public string? DeceasedDateTime { get; set; }\n public Base.ResourceReference? ManagingOrganization { get; set; }\n public bool? DeceasedBoolean { get; set; }\n public Base.HumanName[]? Name { get; set; }\n public string? BirthDate { get; set; }\n public int? MultipleBirthInteger { get; set; }\n public object? MultipleBirth \n {\n get\n {\n if (MultipleBirthBoolean is not null)\n {\n return MultipleBirthBoolean;\n }\n \n if (MultipleBirthInteger is not null)\n {\n return MultipleBirthInteger;\n }\n \n return null;\n }\n \n set\n {\n if (value?.GetType() == typeof(bool))\n {\n MultipleBirthBoolean = (bool)value;\n return;\n }\n \n if (value?.GetType() == typeof(int))\n {\n MultipleBirthInteger = (int)value;\n return;\n }\n \n throw new ArgumentException(\"Invalid type provided\");\n }\n }\n public object? Deceased \n {\n get\n {\n if (DeceasedDateTime is not null)\n {\n return DeceasedDateTime;\n }\n \n if (DeceasedBoolean is not null)\n {\n return DeceasedBoolean;\n }\n \n return null;\n }\n \n set\n {\n if (value?.GetType() == typeof(string))\n {\n DeceasedDateTime = (string)value;\n return;\n }\n \n if (value?.GetType() == typeof(bool))\n {\n DeceasedBoolean = (bool)value;\n return;\n }\n \n throw new ArgumentException(\"Invalid type provided\");\n }\n }\n public Base.Attachment[]? Photo { get; set; }\n public Patient_Link[]? Link { get; set; }\n public bool? Active { get; set; }\n public Patient_Communication[]? Communication { get; set; }\n public Base.Identifier[]? Identifier { get; set; }\n public Base.ContactPoint[]? Telecom { get; set; }\n public Base.ResourceReference[]? GeneralPractitioner { get; set; }\n public string? Gender { get; set; }\n public Base.CodeableConcept? MaritalStatus { get; set; }\n public Patient_Contact[]? Contact { get; set; }\n\n public class Patient_Link : BackboneElement\n {\n public required string Type { get; set; }\n public required Base.ResourceReference Other { get; set; }\n }\n\n public class Patient_Communication : BackboneElement\n {\n public required Base.CodeableConcept Language { get; set; }\n public bool? Preferred { get; set; }\n }\n\n public class Patient_Contact : BackboneElement\n {\n public Base.HumanName? Name { get; set; }\n public string? Gender { get; set; }\n public Base.Period? Period { get; set; }\n public Base.Address? Address { get; set; }\n public Base.ContactPoint[]? Telecom { get; set; }\n public Base.ResourceReference? Organization { get; set; }\n public Base.CodeableConcept[]? Relationship { get; set; }\n }\n}"})))
8891

89-
#_
90-
(deftest generate-search-params
91-
(is
92-
(= (sut/generate-search-params generator fixtures/patient-search-params-schemas
93-
[fixtures/patient-fhir-schema])
94-
[{:path (io/file "search/PatientSearchParameters.cs"),
95-
:content
96-
"namespace Aidbox.FHIR.Search;\n\npublic class PatientSearchParameters : DomainResourceSearchParameters\n{\n public string? Active { get; set; }\n public string? Address { get; set; }\n public string? AddressCity { get; set; }\n public string? AddressCountry { get; set; }\n public string? AddressPostalcode { get; set; }\n public string? AddressState { get; set; }\n public string? AddressUse { get; set; }\n public string? Birthdate { get; set; }\n public string? DeathDate { get; set; }\n public string? Deceased { get; set; }\n public string? Email { get; set; }\n public string? Ethnicity { get; set; }\n public string? Family { get; set; }\n public string? Gender { get; set; }\n public string? GeneralPractitioner { get; set; }\n public string? Given { get; set; }\n public string? Id { get; set; }\n public string? Identifier { get; set; }\n public string? Language { get; set; }\n public string? Link { get; set; }\n public string? Name { get; set; }\n public string? Organization { get; set; }\n public string? PartAgree { get; set; }\n public string? Phone { get; set; }\n public string? Phonetic { get; set; }\n public string? Race { get; set; }\n public string? Telecom { get; set; }\n}"}])))
92+
#_(deftest generate-search-params
93+
(is
94+
(= (sut/generate-search-params generator fixtures/patient-search-params-schemas
95+
[fixtures/patient-fhir-schema])
96+
[{:path (io/file "search/PatientSearchParameters.cs"),
97+
:content
98+
"namespace Aidbox.FHIR.Search;\n\npublic class PatientSearchParameters : DomainResourceSearchParameters\n{\n public string? Active { get; set; }\n public string? Address { get; set; }\n public string? AddressCity { get; set; }\n public string? AddressCountry { get; set; }\n public string? AddressPostalcode { get; set; }\n public string? AddressState { get; set; }\n public string? AddressUse { get; set; }\n public string? Birthdate { get; set; }\n public string? DeathDate { get; set; }\n public string? Deceased { get; set; }\n public string? Email { get; set; }\n public string? Ethnicity { get; set; }\n public string? Family { get; set; }\n public string? Gender { get; set; }\n public string? GeneralPractitioner { get; set; }\n public string? Given { get; set; }\n public string? Id { get; set; }\n public string? Identifier { get; set; }\n public string? Language { get; set; }\n public string? Link { get; set; }\n public string? Name { get; set; }\n public string? Organization { get; set; }\n public string? PartAgree { get; set; }\n public string? Phone { get; set; }\n public string? Phonetic { get; set; }\n public string? Race { get; set; }\n public string? Telecom { get; set; }\n}"}])))
9799

98100
;; TODO
99101
#_(deftest generate-constraints

0 commit comments

Comments
 (0)