diff --git a/src/python-generator/helpers.clj b/src/python-generator/helpers.clj index e375359..c2cb7ad 100644 --- a/src/python-generator/helpers.clj +++ b/src/python-generator/helpers.clj @@ -2,6 +2,7 @@ (:require [python-generator.extractor] [cheshire.core] + [taoensso.nippy :as nippy] [clojure.java.io :as io] [clojure.string :as str])) @@ -81,6 +82,10 @@ (mapv (fn [json-row] (cheshire.core/parse-string json-row keyword)))))) +(defn parse-nippy [path] + (->> (io/file path) + (nippy/thaw-from-file))) + (defn side-effect-map [method, list] (doall (map method list))) (defn create-on-missing [dir] diff --git a/src/python-generator/profile-extractor.clj b/src/python-generator/profile-extractor.clj new file mode 100644 index 0000000..56ec3f1 --- /dev/null +++ b/src/python-generator/profile-extractor.clj @@ -0,0 +1,63 @@ +(ns python-generator.profile-extractor + (:require [cheshire.core] [clojure.string :as str])) + + +(defn filter-base [reduce-keys] + (->> (reduce-keys :structures) + (filter #(not (and (contains? % :from) (contains? % :to)))) + (filter #(not (contains? % :derivation))) + (filter #(not (contains? % :base))) + (filter #(contains? % :elements)) + (map (fn [item] [(item :fqn) item])) + (into {}) + (assoc {} :base) + (hash-map :structures (reduce-keys :structures) :accumulator))) + +(defn filter-element [reduce-keys] + (->> (reduce-keys :structures) + (filter #(str/includes? (or (:base %) "") "Element")) + (filter #(not (str/includes? (or (:base %) "") "BackboneElement"))) + (filter #(not (str/includes? (or (:base %) "") "ElementDefinition"))) + (filter #(contains? % :elements)) + (map (fn [item] [(item :fqn) item])) + (into {}) + (assoc (reduce-keys :accumulator) :element) + (hash-map :structures (reduce-keys :structures) :accumulator))) + +(defn filter-backbone-element [reduce-keys] + (->> (reduce-keys :structures) + (filter #(str/includes? (or (:base %) "") "/BackboneElement")) + (filter #(= "specialization" (:derivation %))) + (map (fn [item] [(item :fqn) item])) + (into {}) + (assoc (reduce-keys :accumulator) :backbone-element) + (hash-map :structures (reduce-keys :structures) :accumulator))) + +(defn filter-domain-resource [reduce-keys] + (->> (reduce-keys :structures) + (filter #(str/includes? (or (:base %) "") "/DomainResource")) + (filter #(= "specialization" (:derivation %))) + (map (fn [item] [(item :fqn) item])) + (into {}) + (assoc (reduce-keys :accumulator) :domain-resource) + (hash-map :structures (reduce-keys :structures) :accumulator))) + +(defn filter-resource [reduce-keys] + (->> (reduce-keys :structures) + (filter #(str/includes? (or (:base %) "") "/Resource")) + (filter #(= "specialization" (:derivation %))) + (map (fn [item] [(item :fqn) item])) + (into {}) + (assoc (reduce-keys :accumulator) :resource) + (hash-map :structures (reduce-keys :structures) :accumulator))) + +(defn filter-constraint [reduce-keys] + (->> (reduce-keys :structures) + (filter #(= "constraint" (:derivation %))) + (filter #(not (= "hl7.fhir.r4.core#4.0.1/Extension" (:base %)))) + (map (fn [item] [(item :fqn) item])) + (into {}) + (assoc (reduce-keys :accumulator) :constraint) + (hash-map :structures (reduce-keys :structures) :accumulator))) + + diff --git a/src/python-generator/profile-helpers.clj b/src/python-generator/profile-helpers.clj new file mode 100644 index 0000000..b65ddb9 --- /dev/null +++ b/src/python-generator/profile-helpers.clj @@ -0,0 +1,98 @@ +(ns python-generator.profile-helpers + (:require + [python-generator.extractor] + [cheshire.core] + [taoensso.nippy :as nippy] + [clojure.java.io :as io] + [clojure.string :as str])) + + +;; TODO: do not hardcode +(def elements #{"HumanName" "Signature" "Range" "Coding" "Attachment" "BackboneElement" "Address" "Money" "Period" "Expression" "TriggerDefinition" "Contributor" "Identifier" "Extension" "Quantity" "RelatedArtifact" "Ratio" "UsageContext" "ContactPoint" "Narrative" "Meta" "SampledData" "Annotation" "Reference" "CodeableConcept" "ContactDetail" "ParameterDefinition" "DataRequirement"}) +(def backbone-elements #{"Population" "Timing" "MarketingStatus" "SubstanceAmount" "ProductShelfLife" "ProdCharacteristic" "Dosage" "ElementDefinition"}) +(def primitives-string #{"dateTime" "xhtml" "Distance" "time" "date" "string" "uuid" "oid" "id" "Dosage" "Duration" "instant" "Count" "decimal" "code" "base64Binary" "unsignedInt" "url" "markdown" "uri" "positiveInt" "canonical" "Age" "Timing"}) + +(defn uppercase-first-letter [string] + (str (str/upper-case (first string)) (subs string 1))) + +(defn escape-keyword [word] + (if (.contains #{"class", "from", "assert", "global", "for", "import"} word) (str word "_") word)) + +(defn string-interpolation [left, right, string] + (str left, string, right)) + +(defn wrap-vector [string] + (string-interpolation "list[", "]", string)) + +(defn wrap-optional [string] + (string-interpolation "Optional[", "]", string)) + +(defn get-resource-name [reference] + (last (str/split (str reference) #"/"))) + +(defn get-type [name type] + (cond + (= type "BackboneElement") (str "" (uppercase-first-letter name)) + (= type "boolean") "bool" + (= type "integer") "int" + (= type "") "str" + (.contains primitives-string type) "str" + :else (or type "str"))) + +(defn derive-basic-type [name element] + (get-type name (get-resource-name (:type element)))) + +(defn append-default-none [string] (str string " = None")) +(defn append-default-vector [string] (str string " = []")) + +(defn transform-element [name element required] + (->> (derive-basic-type name element) + ((if (:array element) wrap-vector str)) + ((if (and (not required) (not (:array element))) wrap-optional str)) + ((if (and (not required) (not (:array element))) append-default-none str)) + ((if (and (not required) (:array element)) append-default-vector str)))) + +(defn elements-to-vector [definition] + (->> (seq (:elements definition)) + (filter (fn [[_, v]] (not (contains? v :choices)))))) + +(defn get-parent [base-reference] + (->> (get-resource-name base-reference) + (string-interpolation "(" ")"))) + +(defn collect-types [parent_name, required, [k, v]] + (hash-map :name (escape-keyword (name k)) :base parent_name :value (transform-element (str parent_name "_" (uppercase-first-letter (name k))) v (.contains required (name k))))) + +(defn resolve-backbone-elements [[k, v]] + (if (= (get-resource-name (:type v)) "BackboneElement") (vector k, v) (vector))) + +(defn get-typings-and-imports [parent_name, required, data] + (reduce (fn [acc, item] + (hash-map :elements (conj (:elements acc) (collect-types parent_name required item)) + :backbone-elements (conj (:backbone-elements acc) (resolve-backbone-elements item)))) + (hash-map :elements [] :backbone-elements []) data)) + +(defn parse-ndjson-gz [path] + (with-open [rdr (-> path + (io/input-stream) + (java.util.zip.GZIPInputStream.) + (io/reader))] + (->> rdr + line-seq + (mapv (fn [json-row] + (cheshire.core/parse-string json-row keyword)))))) + +(defn parse-nippy [path] + (->> (io/file path) + (nippy/thaw-from-file))) + +(defn side-effect-map [method, list] (doall (map method list))) + +(defn create-on-missing [dir] + (when-not (.exists (io/file dir)) (.mkdir (io/file dir)))) + +(defn write-to-file [directory, filename, text] + (create-on-missing directory) + (with-open [writer (io/writer (io/file directory (str filename ".py")))] (.write writer text))) + +;; (get-resource-name filename) \ No newline at end of file diff --git a/src/python-generator/profile-parser.clj b/src/python-generator/profile-parser.clj new file mode 100644 index 0000000..1a89811 --- /dev/null +++ b/src/python-generator/profile-parser.clj @@ -0,0 +1,146 @@ +(ns python-generator.profile-parser + (:require + [python-generator.profile-extractor :as ext] + [python-generator.profile-helpers :as help] + [python-generator.profile-resources :as gen] + [cheshire.core] + [clojure.string :as str])) + +;; (str parent_name "_" (help/uppercase-first-letter (name property_name))) + +(defn compile-backbone [parent_name property_name definition] + (let [name "NaMe" + data (help/get-typings-and-imports name (or (:required definition) []) (help/elements-to-vector definition)) + backbone-elements (filter (fn [item] (> (count item) 0)) (:backbone-elements data))] + (conj data (hash-map :backbone-elements (if (= (count backbone-elements) 0) [] (map (fn [[k, v]] (compile-backbone name k v)) backbone-elements)))))) + +(defn test [name data] + (->> (filter (fn [item] (> (count item) 0)) (:backbone-elements data)) + (map (fn [[k, v]] (compile-backbone name k v))) + (hash-map :backbone-elements) + (conj data))) + + + +;; (helpers/get-resource-name (:type definition)) + +(defn attach-parent-data [parent a context child] + (if (nil? parent) child (conj child (hash-map :elements (concat (get-in context [:classes parent a :elements]) (:elements child)))))) + +(defn compile-elements [property parent context] + (->> (map (fn [[name definition]] + (->> (help/elements-to-vector definition) + (help/get-typings-and-imports (:type definition) (or (:required definition) [])) + (test "ktulh-ftagn") + (attach-parent-data parent (:base definition) context) + (conj (hash-map :name name :source (:base definition))) + (hash-map name))) (property (:accumulator context))) + #_(apply merge))) + + +(defn group-by [ig] + (->> (ext/filter-base ig) + (ext/filter-element) + (ext/filter-resource) + (ext/filter-backbone-element) + (ext/filter-domain-resource) + (ext/filter-constraint))) + +(defn c-base [context] + (->> context + (compile-elements :base nil) + (hash-map :base) + (hash-map :classes) + (conj context))) + +(defn c-resource [context] + (->> context + (compile-elements :resource :base) + (hash-map :resource) + (conj (:classes context)) + (hash-map :classes) + (conj context))) + +(defn c-domain-resource [context] + (->> context + (compile-elements :domain-resource :resource) + (hash-map :domain-resource) + (conj (:classes context)) + (hash-map :classes) + (conj context))) + +(defn c-constraint [context] + (->> context + (compile-elements :constraint :domain-resource) + (hash-map :constraint) + (conj (:classes context)) + (hash-map :classes) + (conj context))) + +(defn compile [context] + (->> context + (c-base) + (c-resource) + (c-domain-resource) + (c-constraint))) + +(defn compile-profiles [] + (->> (hash-map :structures (help/parse-ndjson-gz "/Users/gena.razmakhnin/Documents/aidbox-sdk-js/fhir-schema/hl7.fhir.r4.core#4.0.1/package.ndjson.gz")) + (group-by) + (compile) + (:classes) + (:domain-resource))) + +(compile-profiles) + + + + + + + + +;; (filter (fn [item] (:elements item)) [{:elements {:a {:type 1} :b {:type 2} :c {:type 2}}}]) + +#_(->> (helpers/parse-ndjson-gz "/Users/gena.razmakhnin/Documents/aidbox-python-tooklit/fhir-schema/hl7.fhir.r4.core#4.0.1_package.ndjson.gz") + (filter #(= "constraint" (:derivation %))) + (filter (fn [item] ())) + (filter (fn [item] + (some + (fn [[_ value]] (and (= "hl7.fhir.r4.core#4.0.1/CodeableConcept" (:type value)) (contains? value :binding))) + (:elements item))))) + +;; (map (fn [[key value]] key) {"hello" 3 "hello2" 3}) +;; (type (hash-map "hello" 3 "hello2" 3)) +;; ((hash-map "hello" 1 "hello2" 2) "hello2") + +#_(->> (helpers/parse-nippy "/Users/gena.razmakhnin/Documents/aidbox-python-tooklit/fhir-schema/hl7.fhir.r4.core#4.0.1_terminology-index.nippy") + #_(:valuesets) + (:codesystems) + #_(map (fn [[key value]] value)) + #_(filter (fn [[key value]] (= key "http://terminology.hl7.org/CodeSystem/v2-0334"))) + ((fn [map] (map "http://loinc.org")))) + +;; (map (fn [item] (print item))) +;; (keyword "http://hl7.org/fhir/supplydelivery-status") +;; (keyword "http://hl7.org/fhir/ValueSet/ldlcholesterol-codes") + + +;; :fhirVersions ["4.0.1"] +;; :name "hl7.fhir.r4.core" +;; :type "fhir.core" +;; :version "4.0.1" +;; :dependencies [] + +;; :fhirVersions ["4.0.1"] +;; :name "hl7.fhir.us.core" +;; :type "IG" +;; :version "5.0.1" +;; :dependencies [":hl7.fhir.r4.core#4.0.1" ":hl7.terminology.r4#3.1.0" ":hl7.fhir.uv.bulkdata#2.0.0" ":hl7.fhir.uv.smart-app-launch#2.0.0" ":us.nlm.vsac#0.7.0" ":hl7.fhir.uv.sdc#3.0.0"] + +;; :fhirVersions ["4.0.1"] +;; :name "hl7.fhir.us.mcode" +;; :type "IG" +;; :version "3.0.0" +;; :dependencies [":hl7.fhir.r4.core#4.0.1" ":hl7.terminology.r4#5.3.0" ":hl7.fhir.us.core#5.0.1" ":hl7.fhir.uv.genomics-reporting#2.0.0" ":hl7.fhir.uv.extensions#1.0.0"] + diff --git a/src/python-generator/profile-resources.clj b/src/python-generator/profile-resources.clj new file mode 100644 index 0000000..5728504 --- /dev/null +++ b/src/python-generator/profile-resources.clj @@ -0,0 +1,37 @@ +(ns python-generator.profile-resources + (:require + [cheshire.core] + [clojure.string :as str] + [python-generator.extractor :as extractors] + [python-generator.profile-helpers :as help])) + +(defn filter-backbone-elements [data] + (filter (fn [item] (> (count item) 0)) (:backbone-elements data))) + +(defn compile-backbone [parent_name property_name definition] + (let [name (str parent_name "_" (help/uppercase-first-letter (name property_name))) + data (help/get-typings-and-imports name (or (:required definition) []) (help/elements-to-vector definition)) + backbone-elements (filter-backbone-elements data)] + (->> (str (clojure.string/join "\n" (:elements data)) "\n\n") + (str "class " name "(BackboneElement):\n") + (str (if (= (count backbone-elements) 0) (str) (str/join (map (fn [[k, v]] (str (compile-backbone name k v))) backbone-elements))))))) + +(defn test [name data] + (->> (filter (fn [item] (> (count item) 0)) (:backbone-elements data)) + (map (fn [[k, v]] (str (compile-backbone name k v)))) + (str/join))) + +(defn combine-file [definition, data] + (->> (str (clojure.string/join "\n" (:elements data)) "\n\n") + (str "class " (help/get-resource-name (:type definition)) (help/get-parent (:base definition)) ":\n") + #_(str (test (help/get-resource-name (:type definition)) data)) + #_(str "from ..base import *\n\n") + #_(str "from typing import Optional\n"))) + +(defn compile-single-class [directory] + (fn [[_ definition]] + (->> (help/elements-to-vector definition) + (help/get-typings-and-imports (help/get-resource-name (:type definition)) (or (:required definition) [])) + #_(combine-file definition) + #_(str/join)))) +