From 1ac5b2220f6d5d8cfe883bf894b37298630b20c3 Mon Sep 17 00:00:00 2001 From: wkarwacki Date: Sun, 11 Aug 2024 22:26:53 +0200 Subject: [PATCH] auto-generated stub implementation --- src/lib.rs | 10 +- src/lib/context.rs | 23 ++-- src/lib/def.rs | 8 +- src/lib/gen/gen.rs | 12 +- src/lib/gen/lang.rs | 11 +- .../python/client/gen_python_http_client.rs | 102 +++++++++++++---- src/lib/gen/python/client/templates/dto.hbs | 17 ++- .../gen/python/client/templates/stubImpl.hbs | 3 + src/lib/gen/python/client/templates/union.hbs | 4 +- src/lib/gen/python/lang_python.rs | 18 +++ .../python/server/gen_python_http_server.rs | 104 ++++++++++++++---- src/lib/gen/python/server/templates/dto.hbs | 15 ++- .../gen/python/server/templates/service.hbs | 4 +- .../gen/python/server/templates/stubImpl.hbs | 3 + src/lib/gen/template/proc.rs | 47 +++++++- 15 files changed, 287 insertions(+), 94 deletions(-) create mode 100644 src/lib/gen/python/client/templates/stubImpl.hbs create mode 100644 src/lib/gen/python/server/templates/stubImpl.hbs diff --git a/src/lib.rs b/src/lib.rs index c725e1b..f89fed3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,13 +136,15 @@ pub enum Layout { Tag, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GenCfg { #[serde(default)] type_mapping: HashMap, module: Option, dto_name: Option, + #[serde(default)] + auto_implement: bool, } #[derive(Clone, ValueEnum)] @@ -189,11 +191,7 @@ pub fn do_main(cli: Cli) { } => { let generator_config = config .map(|c| serde_yaml::from_reader::(File::open(c).unwrap()).unwrap()) - .unwrap_or(GenCfg { - type_mapping: HashMap::new(), - module: None, - dto_name: None, - }); + .unwrap_or(Default::default()); gen(&input, &lang, &role, &generator_config, &templates_path) .iter() .for_each(|(path, content)| { diff --git a/src/lib/context.rs b/src/lib/context.rs index 7ffa6ff..a97c19a 100644 --- a/src/lib/context.rs +++ b/src/lib/context.rs @@ -61,28 +61,21 @@ impl Context { serde_yaml::from_value(value.clone()).unwrap() } - pub(crate) fn op_refs(&self, op: &Op) -> Vec<(Option, Vec)> { + pub(crate) fn op_refs(&self, op: &Op) -> HashMap, Vec> { let refs = op.refs(); - self.src_to_refs(&refs) + Self::src_to_refs(refs) } - pub(crate) fn def_refs(&self, def: &Def) -> Vec<(Option, Vec)> { + pub(crate) fn def_refs(&self, def: &Def) -> HashMap, Vec> { let refs = def.refs(); - self.src_to_refs(&refs) + Self::src_to_refs(refs) } - fn src_to_refs(&self, refs: &Vec) -> Vec<(Option, Vec)> { - let grouped = refs.iter().into_group_map_by(|r#ref| r#ref.src.clone()); - grouped + fn src_to_refs(refs: Vec) -> HashMap, Vec> { + refs.iter() + .into_group_map_by(|r#ref| r#ref.src.clone()) .iter() - .map(|(src, refs)| { - ( - src.clone(), - refs.iter() - .map(|r#ref| r#ref.class_name()) - .collect::>(), - ) - }) + .map(|(src, refs)| (src.clone(), refs.iter().cloned().cloned().collect())) .collect() } } diff --git a/src/lib/def.rs b/src/lib/def.rs index d10e1b8..6bda48e 100644 --- a/src/lib/def.rs +++ b/src/lib/def.rs @@ -48,7 +48,7 @@ pub(crate) struct Alias { pub r#ref: Ref, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) struct Bool { #[serde(default)] #[serde(skip_serializing_if = "<&bool>::not")] @@ -62,7 +62,7 @@ pub(crate) struct Const { pub desc: Option>, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) struct Dec { #[serde(default)] #[serde(skip_serializing_if = "<&bool>::not")] @@ -83,7 +83,7 @@ pub(crate) enum EnumVals { Str(Vec), } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) struct Int { #[serde(default)] #[serde(skip_serializing_if = "<&bool>::not")] @@ -155,7 +155,7 @@ pub(crate) struct Seq { pub null: bool, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub(crate) struct Str { #[serde(default)] #[serde(skip_serializing_if = "<&bool>::not")] diff --git a/src/lib/gen/gen.rs b/src/lib/gen/gen.rs index 4d8dd2f..af44462 100644 --- a/src/lib/gen/gen.rs +++ b/src/lib/gen/gen.rs @@ -1,5 +1,5 @@ use crate::{ - gen::template::r#fn::ToFlatCase, + gen::template::{proc::StubImpl, r#fn::ToFlatCase}, lib::{ context::Context, gen::{ @@ -87,6 +87,16 @@ pub(crate) fn go( handlebars.register_helper("toFlatCase", Box::new(ToFlatCase {})); handlebars.register_helper("typeParams", Box::new(TypeParams {}.clone())); handlebars.register_helper("valueDef", Box::new(ValueDef {}.clone())); + handlebars.register_helper( + "stubImpl", + Box::new( + StubImpl { + gen: gen.clone(), + context: context.clone(), + } + .clone(), + ), + ); handlebars_misc_helpers::setup_handlebars(&mut handlebars); handlebars.set_strict_mode(false); diff --git a/src/lib/gen/lang.rs b/src/lib/gen/lang.rs index 790267c..88c63d5 100644 --- a/src/lib/gen/lang.rs +++ b/src/lib/gen/lang.rs @@ -1,4 +1,4 @@ -use crate::lib::{def::Def, desc::Desc, r#ref::Ref}; +use crate::lib::{context::Context, def::Def, desc::Desc, r#ref::Ref}; use handlebars::{Handlebars, JsonValue}; use std::path::PathBuf; @@ -15,6 +15,7 @@ pub(crate) trait Lang { fn fmt_src(&self, src: &str) -> String; fn fmt_type(&self, def: &Def, name: &Option<&str>) -> String; fn fmt_value(&self, json_value: &JsonValue) -> String; + fn stub_impl(&self, def: &Def, context: &Context) -> String; } pub static DTO_NAME_TEMPLATE_NAME: &str = "dtoName"; @@ -39,3 +40,11 @@ pub(crate) fn inner( Desc::TypeParam { .. } => unimplemented!("Type parameter not supported yet."), } } + +pub(crate) fn stub_impl(lang: &Box, desc: &Desc, context: &Context) -> String { + match desc { + Desc::Def(def) => lang.stub_impl(def, context), + Desc::Ref(r#ref) => lang.stub_impl(&context.resolve(r#ref), context), + Desc::TypeParam { .. } => unimplemented!("Type parameter not supported yet."), + } +} diff --git a/src/lib/gen/python/client/gen_python_http_client.rs b/src/lib/gen/python/client/gen_python_http_client.rs index b43279e..1283a40 100644 --- a/src/lib/gen/python/client/gen_python_http_client.rs +++ b/src/lib/gen/python/client/gen_python_http_client.rs @@ -117,20 +117,42 @@ impl Gen for GenPythonHttpClient { let imports = context .def_refs(def) .iter() - .flat_map(|(src, names)| { - names.iter().map(move |name| { - "from ".to_string() + .flat_map(|(src, refs)| { + refs.iter().flat_map(move |r#ref| { + let mut names: Vec = Default::default(); + names.push("from ".to_string() + self.lang.module().as_str() + "." - + match src { + + match &src { None => self.lang.feature.clone().to_case(Case::Snake), - Some(src) => self.lang.fmt_src(src), + Some(src) => self.lang.fmt_src(src.as_str()), } .as_str() + "." - + name.to_case(Case::Snake).as_str() + + r#ref.class_name().to_case(Case::Snake).as_str() + " import " - + dto_name(self.lang.fmt_class(name, &None).as_str(), &self.lang()).as_str() + + dto_name(self.lang.fmt_class(r#ref.class_name().as_str(), &None).as_str(), &self.lang()) + .as_str()); + match context.resolve(r#ref) { + Def::Obj(obj) => { + obj.adt.iter().for_each(|_| names.push("from ".to_string() + + self.lang.module().as_str() + + "." + + match &src { + None => self.lang.feature.clone().to_case(Case::Snake), + Some(src) => self.lang.fmt_src(src.as_str()), + } + .as_str() + + "." + + r#ref.class_name().to_case(Case::Snake).as_str() + + " import " + + dto_name(self.lang.fmt_class(r#ref.class_name().as_str(), &None).as_str(), &self.lang()) + .as_str() + + "Base")) + } + _ => {} + } + names }) }) .unique() @@ -178,23 +200,57 @@ impl Gen for GenPythonHttpClient { pkg.ops .iter() .flat_map(|(_, ops)| ops.iter().flat_map(|op| context.op_refs(op))) - .flat_map(|(src, names)| { - names - .iter() - .map(move |name| { - "from ".to_string() - + self.lang.module().as_str() - + "." - + match &src { - None => self.lang.feature.clone().to_case(Case::Snake), - Some(src) => self.lang.fmt_src(src.as_str()), - } - .as_str() - + "." - + name.to_case(Case::Snake).as_str() - + " import " - + dto_name(self.lang.fmt_class(name, &None).as_str(), &self.lang()) + .flat_map(|(src, refs)| { + refs.iter() + .flat_map(|r#ref| { + let mut names: Vec = Default::default(); + names.push( + "from ".to_string() + + self.lang.module().as_str() + + "." + + match &src { + None => self.lang.feature.clone().to_case(Case::Snake), + Some(src) => self.lang.fmt_src(src.as_str()), + } .as_str() + + "." + + r#ref.class_name().to_case(Case::Snake).as_str() + + " import " + + dto_name( + self.lang + .fmt_class(r#ref.class_name().as_str(), &None) + .as_str(), + &self.lang(), + ) + .as_str(), + ); + match context.resolve(r#ref) { + Def::Obj(obj) => obj.adt.iter().for_each(|_| { + names.push( + "from ".to_string() + + self.lang.module().as_str() + + "." + + match &src { + None => self.lang.feature.clone().to_case(Case::Snake), + Some(src) => self.lang.fmt_src(src.as_str()), + } + .as_str() + + "." + + r#ref.class_name().to_case(Case::Snake).as_str() + + " import " + + dto_name( + self.lang + .fmt_class(r#ref.class_name().as_str(), &None) + .as_str(), + &self.lang(), + ) + .as_str() + + "Base", + ) + }), + _ => {} + } + names }) .collect::>() }) diff --git a/src/lib/gen/python/client/templates/dto.hbs b/src/lib/gen/python/client/templates/dto.hbs index 931700b..ee65d8a 100644 --- a/src/lib/gen/python/client/templates/dto.hbs +++ b/src/lib/gen/python/client/templates/dto.hbs @@ -1,9 +1,11 @@ -{{#if val.item}}{{>dto key=(add key "Item") val=val.item tl=false}} +{{#if val.item}}{{>dto key=(add key "Item") val=val.item tl=false prefix=prefix}} -{{else if val.val}}{{>dto key=(add key "Value") val=val.val tl=false}} +{{else if val.val}}{{>dto key=(add key "Value") val=val.val tl=false prefix=prefix}} {{/if}}{{#if val.adt}}class {{key}}Base(Dto): - {{indent}}{{#unless val.vars}}pass{{/unless}}{{#each val.vars}}{{#if (or (eq this.type "obj") this.item this.val)}}{{>dto key=(fmtClass @key) val=this indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}} + {{indent}} @classmethod + {{indent}} def get(cls) -> '{{fmtClass key}}': + {{indent}} return {{#with prefix}}{{this}}.{{/with}}{{#each val.adt.map}}{{#if @first}}{{../key}}{{fmtClass @key}}.get(){{/if}}{{/each}}{{#each val.vars}}{{#if (or (eq this.type "obj") this.item this.val)}}{{>dto key=(fmtClass @key) val=this indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}} {{indent}}{{#if (eq @key ../val.adt.var)}}{{fmtName @key}}: {{#each../val.adt.map}}Literal[{{json @key}}]{{#unless @last}} | {{/unless}}{{/each}} {{else}}{{../indent}}{{>var}}{{/if}}{{/each}} {{#each val.adt.map}} @@ -14,8 +16,11 @@ {{/each}} -{{key}} = {{>union}} {{else if (and (isAlias val) tl)}}{{key}} = {{fmtType val key}}{{else if (or val.vars adtParent)}}class {{adtParent}}{{fmtClass key}}({{#if (parents val)}}{{#each (parents val)}}{{fmtType this}}{{#unless @last}}, {{/unless}}{{/each}}{{else if adtParent}}{{adtParent}}Base{{else}}Dto{{/if}}){{>typeParams val=val}}: -{{indent}}{{#unless val.vars}} pass{{/unless}}{{#each val.vars}}{{#if (or (or (eq this.type "obj") (eq this.type "enum")) this.item this.val)}} {{>dto key=(fmtClass @key) val=this dtoFormLike=(or ../formLike ../dtoFormLike) indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}}{{#if (or (not ../this.formLike) (not (eq @key "file")))}} +{{key}} ={{>union}}{{else if (and (isAlias val) tl)}}{{key}} = {{fmtType val key}}{{else if (or val.vars adtParent)}}class {{adtParent}}{{fmtClass key}}({{#if (parents val)}}{{#each (parents val)}}{{fmtType this}}{{#unless @last}}, {{/unless}}{{/each}}{{else if adtParent}}{{adtParent}}Base{{else}}Dto{{/if}}){{>typeParams val=val}}: +{{indent}} @classmethod +{{indent}} def get(cls) -> '{{adtParent}}{{key}}': +{{indent}} return {{#with prefix}}{{this}}.{{/with}}{{>stubImpl key=(add adtParent key) val=val indent=(add indent " ")}} +{{indent}}{{#each val.vars}}{{#if (or (or (eq this.type "obj") (eq this.type "enum")) this.item this.val)}} {{>dto key=(fmtClass @key) val=this dtoFormLike=(or ../formLike ../dtoFormLike) indent=(add ../indent " ") prefix=../key}}{{/if}}{{/each}}{{#each val.vars}}{{#if (or (not ../this.formLike) (not (eq @key "file")))}} {{../indent}}{{>var}}{{/if}}{{/each}}{{else if (eq val.type "enum")}}class {{key}}({{#with (valueDef val.vars.[0])}}{{#if (eq type "int")}}IntEnum{{else}}str, Enum{{/if}}{{/with}}):{{#each val.vals}} {{fmtEnum this}} = {{json this}}{{/each}}{{/if}}{{#if (and (and (hasKey this "tl") (not tl)) (and dtoFormLike (eq val.type "obj")))}} @@ -24,4 +29,4 @@ {{../indent}}return {{fmtName key}}.model_dump_json(exclude_none=True){{/if}}{{#if formLike}} def form(self) -> dict[str, Any]: - return self.model_dump(exclude_none=True){{/if}} + return self.model_dump(exclude_none=True){{/if}}{{#if (and (eq val.type "obj") (and (not val.adt) (not (typeParams val))))}}{{/if}} diff --git a/src/lib/gen/python/client/templates/stubImpl.hbs b/src/lib/gen/python/client/templates/stubImpl.hbs new file mode 100644 index 0000000..add9065 --- /dev/null +++ b/src/lib/gen/python/client/templates/stubImpl.hbs @@ -0,0 +1,3 @@ +{{#if (and val.path (not (eq val.type "alias")))}}{{#with (resolve val)}}{{#if (eq this.type "obj")}}{{#if this.adt}}{{>dtoName val=(fmtClass (fmtName ../val.path))}}Base{{else}}{{>dtoName val=(fmtClass ../val.path)}}{{/if}}.get(){{else}}{{>stubImpl val=this indent=indent}}{{/if}}{{/with}}{{else if (or (eq val.type "obj") val.vars)}}{{key}}({{#each val.vars}} + {{../indent}} {{fmtName @key}}={{>stubImpl val=this key=(fmtClass @key) indent=(add ../indent " ") prefix=../key}},{{/each}} + {{indent}}){{else if (eq val.type "map")}}{ {{#if val.key}}{{#if (and prefix (not val.key.path))}}{{prefix}}.{{/if}}{{>stubImpl val=val.key key=(add key "Key") indent=indent}}{{else}}{{>stubImpl val="str" key=(add key "Key") indent=indent}}{{/if}}: {{#if val.val}}{{#if (and prefix (not val.val.path))}}{{prefix}}.{{/if}}{{>stubImpl val=val.val key=(add key "Val") indent=indent}}{{else}}{{>stubImpl val="str" key=(add key "Val") indent=indent}}{{/if}} }{{else if (eq val.type "seq")}}[{{#if (eq val.item.type "obj")}}{{#if prefix}}{{prefix}}.{{/if}}{{add key "Item"}}.get(){{else}}{{>stubImpl val=val.item indent=indent prefix=null}}{{/if}}]{{else if val.mix}}{{unimplemented}}{{else}}{{stubImpl val indent=indent}}{{/if}} \ No newline at end of file diff --git a/src/lib/gen/python/client/templates/union.hbs b/src/lib/gen/python/client/templates/union.hbs index 776c607..41e971e 100644 --- a/src/lib/gen/python/client/templates/union.hbs +++ b/src/lib/gen/python/client/templates/union.hbs @@ -1,4 +1,4 @@ -Annotated[ + Annotated[ {{#each val.adt.map}}{{../key}}{{fmtClass @key}}{{#unless @last}} | {{/unless}}{{/each}}, Field(discriminator="{{fmtName val.adt.var}}") -] +] \ No newline at end of file diff --git a/src/lib/gen/python/lang_python.rs b/src/lib/gen/python/lang_python.rs index 1d67952..4d1ee28 100644 --- a/src/lib/gen/python/lang_python.rs +++ b/src/lib/gen/python/lang_python.rs @@ -1,5 +1,6 @@ use crate::{ lib::{ + context::Context, def::{Def, Enum, EnumVals}, desc::Desc, gen::{ @@ -190,4 +191,21 @@ impl Lang for LangPython { Value::Null => "None".to_string(), } } + + fn stub_impl(&self, def: &Def, context: &Context) -> String { + match def { + Def::Alias(alias) => self.stub_impl(&context.resolve(&alias.r#ref), context), + Def::Bool(_) => "False".to_string(), + Def::Const(r#const) => serde_json::to_string(&r#const.clone().val).unwrap(), + Def::Dec(_) => "0.0".to_string(), + Def::Enum(Enum { vals, null: _ }) => match vals { + EnumVals::Int(vals) => vals.first().unwrap().to_string(), + EnumVals::Str(vals) => "\"".to_string() + vals.first().unwrap() + "\"", + }, + Def::Int(_) => "0".to_string(), + Def::Str(_) => "\"\"".to_string(), + Def::Struct(_) => "{}".to_string(), + _ => unreachable!(), + } + } } diff --git a/src/lib/gen/python/server/gen_python_http_server.rs b/src/lib/gen/python/server/gen_python_http_server.rs index eb10a37..f1d2020 100644 --- a/src/lib/gen/python/server/gen_python_http_server.rs +++ b/src/lib/gen/python/server/gen_python_http_server.rs @@ -116,20 +116,42 @@ impl Gen for GenPythonHttpServer { let imports = context .def_refs(def) .iter() - .flat_map(|(src, names)| { - names.iter().map(move |name| { - "from ".to_string() + .flat_map(|(src, refs)| { + refs.iter().flat_map(move |r#ref| { + let mut names: Vec = Default::default(); + names.push("from ".to_string() + self.lang.module().as_str() + "." - + match src { + + match &src { None => self.lang.feature.clone().to_case(Case::Snake), - Some(src) => self.lang.fmt_src(src), + Some(src) => self.lang.fmt_src(src.as_str()), } .as_str() + "." - + name.to_case(Case::Snake).as_str() + + r#ref.class_name().to_case(Case::Snake).as_str() + " import " - + dto_name(self.lang.fmt_class(name, &None).as_str(), &self.lang()).as_str() + + dto_name(self.lang.fmt_class(r#ref.class_name().as_str(), &None).as_str(), &self.lang()) + .as_str()); + match context.resolve(r#ref) { + Def::Obj(obj) => { + obj.adt.iter().for_each(|_| names.push("from ".to_string() + + self.lang.module().as_str() + + "." + + match &src { + None => self.lang.feature.clone().to_case(Case::Snake), + Some(src) => self.lang.fmt_src(src.as_str()), + } + .as_str() + + "." + + r#ref.class_name().to_case(Case::Snake).as_str() + + " import " + + dto_name(self.lang.fmt_class(r#ref.class_name().as_str(), &None).as_str(), &self.lang()) + .as_str() + + "Base")) + } + _ => {} + } + names }) }) .unique() @@ -177,23 +199,57 @@ impl Gen for GenPythonHttpServer { pkg.ops .iter() .flat_map(|(_, ops)| ops.iter().flat_map(|op| context.op_refs(op))) - .flat_map(|(src, names)| { - names - .iter() - .map(move |name| { - "from ".to_string() - + self.lang.module().as_str() - + "." - + match &src { - None => self.lang.feature.clone().to_case(Case::Snake), - Some(src) => self.lang.fmt_src(src.as_str()), - } - .as_str() - + "." - + name.to_case(Case::Snake).as_str() - + " import " - + dto_name(self.lang.fmt_class(name, &None).as_str(), &self.lang()) + .flat_map(|(src, refs)| { + refs.iter() + .flat_map(|r#ref| { + let mut names: Vec = Default::default(); + names.push( + "from ".to_string() + + self.lang.module().as_str() + + "." + + match &src { + None => self.lang.feature.clone().to_case(Case::Snake), + Some(src) => self.lang.fmt_src(src.as_str()), + } .as_str() + + "." + + r#ref.class_name().to_case(Case::Snake).as_str() + + " import " + + dto_name( + self.lang + .fmt_class(r#ref.class_name().as_str(), &None) + .as_str(), + &self.lang(), + ) + .as_str(), + ); + match context.resolve(r#ref) { + Def::Obj(obj) => obj.adt.iter().for_each(|_| { + names.push( + "from ".to_string() + + self.lang.module().as_str() + + "." + + match &src { + None => self.lang.feature.clone().to_case(Case::Snake), + Some(src) => self.lang.fmt_src(src.as_str()), + } + .as_str() + + "." + + r#ref.class_name().to_case(Case::Snake).as_str() + + " import " + + dto_name( + self.lang + .fmt_class(r#ref.class_name().as_str(), &None) + .as_str(), + &self.lang(), + ) + .as_str() + + "Base", + ) + }), + _ => {} + } + names }) .collect::>() }) @@ -254,7 +310,7 @@ impl Gen for GenPythonHttpServer { let service = handlebars .render_template( service_template.as_str(), - &json!({"feature": self.lang.feature.clone(), "ops": &pkg.ops}), + &json!({"feature": self.lang.feature.clone(), "ops": &pkg.ops, "autoImpl": self.lang.gen_cfg.auto_implement}) ) .unwrap(); ( diff --git a/src/lib/gen/python/server/templates/dto.hbs b/src/lib/gen/python/server/templates/dto.hbs index e9d53ec..269b545 100644 --- a/src/lib/gen/python/server/templates/dto.hbs +++ b/src/lib/gen/python/server/templates/dto.hbs @@ -1,9 +1,11 @@ -{{#if val.item}}{{>dto key=(add key "Item") val=val.item tl=false}} +{{#if val.item}}{{>dto key=(add key "Item") val=val.item tl=false prefix=prefix}} -{{else if val.val}}{{>dto key=(add key "Value") val=val.val tl=false}} +{{else if val.val}}{{>dto key=(add key "Value") val=val.val tl=false prefix=prefix}} {{/if}}{{#if val.adt}}class {{key}}Base(Dto): - {{indent}}{{#unless val.vars}}pass{{/unless}}{{#each val.vars}}{{#if (or (eq this.type "obj") this.item this.val)}}{{>dto key=(fmtClass @key) val=this indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}} + {{indent}} @classmethod + {{indent}} def get(cls) -> '{{fmtClass key}}': + {{indent}} return {{#with prefix}}{{this}}.{{/with}}{{#each val.adt.map}}{{#if @first}}{{../key}}{{fmtClass @key}}.get(){{/if}}{{/each}}{{#each val.vars}}{{#if (or (eq this.type "obj") this.item this.val)}}{{>dto key=(fmtClass @key) val=this indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}} {{indent}}{{#if (eq @key ../val.adt.var)}}{{fmtName @key}}: {{#each../val.adt.map}}Literal[{{json @key}}]{{#unless @last}} | {{/unless}}{{/each}} {{else}}{{../indent}}{{>var}}{{/if}}{{/each}} {{#each val.adt.map}} @@ -14,8 +16,11 @@ {{/each}} -{{key}} = {{>union}} {{else if (and (isAlias val) tl)}}{{key}} = {{fmtType val key}}{{else if (or val.vars adtParent)}}class {{adtParent}}{{fmtClass key}}({{#each (parents val)}}{{fmtType this}}{{#unless @last}}, {{/unless}}{{/each}}{{#if adtParent}}{{#if (parents val)}}, {{/if}}{{adtParent}}Base{{/if}}{{#unless (or (parents val) adtParent)}}Dto{{/unless}}){{>typeParams val=val}}: -{{indent}}{{#unless val.vars}} pass{{/unless}}{{#each val.vars}}{{#if (or (or (eq this.type "obj") (eq this.type "enum")) this.item this.val)}} {{>dto key=(fmtClass @key) val=this indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}} +{{key}} ={{>union}} {{else if (and (isAlias val) tl)}}{{key}} = {{fmtType val key}}{{else if (or val.vars adtParent)}}class {{adtParent}}{{fmtClass key}}({{#each (parents val)}}{{fmtType this}}{{#unless @last}}, {{/unless}}{{/each}}{{#if adtParent}}{{#if (parents val)}}, {{/if}}{{adtParent}}Base{{/if}}{{#unless (or (parents val) adtParent)}}Dto{{/unless}}){{>typeParams val=val}}: +{{indent}} @classmethod +{{indent}} def get(cls) -> '{{adtParent}}{{key}}': +{{indent}} return {{#with prefix}}{{this}}.{{/with}}{{>stubImpl key=(add adtParent key) val=val indent=(add indent " ")}} +{{indent}}{{#each val.vars}}{{#if (or (or (eq this.type "obj") (eq this.type "enum")) this.item this.val)}} {{>dto key=(fmtClass @key) val=this prefix=../key indent=(add ../indent " ")}}{{/if}}{{/each}}{{#each val.vars}} {{../indent}}{{>var}}{{/each}}{{else if (eq val.type "enum")}}class {{key}}({{#with (valueDef val.vars.[0])}}{{#if (eq type "int")}}IntEnum{{else}}str, Enum{{/if}}{{/with}}):{{#each val.vals}} {{fmtEnum this}} = {{json this}}{{/each}}{{/if}} {{#if formLike}} @classmethod diff --git a/src/lib/gen/python/server/templates/service.hbs b/src/lib/gen/python/server/templates/service.hbs index 2a8d1fe..119a30f 100644 --- a/src/lib/gen/python/server/templates/service.hbs +++ b/src/lib/gen/python/server/templates/service.hbs @@ -13,7 +13,7 @@ class {{to_pascal_case feature}}Service(ABC): {{/each}} }{{/if}} - @abstractmethod + {{#unless ../../autoImpl}}@abstractmethod{{/unless}} def {{to_snake_case this.name}}(self{{#if this.req}}, {{#if this.req.path}}{{fmtName this.req.path}}: {{>dtoName val=(fmtClass this.req.path)}}{{else}}{{#if (eq this.req.form "multipart/form-data")}}{{fmtName (add this.name "Req")}}: {{>dtoName val=(fmtClass (add this.name "Req"))}}{{else if (or (eq this.req.type "obj") (eq this.req.type "seq") (eq this.req.type "map"))}}{{fmtName (add this.name "Req")}}: {{fmtName (add this.name "Req")}}.{{>dtoName val=(fmtClass (add this.name "Req"))}}{{else}}request: {{fmtType this.req}}{{/if}}{{/if}}{{/if}}{{#each this.params}}, {{to_snake_case this.name}}: {{#if this.opt}}{{fmtOpt (fmtType this)}}{{else}}{{fmtType this}}{{/if}}{{/each}}) -> {{#if this.res}}{{#if (filterNonconst this.res.meta)}}Tuple[{{/if}}{{#if (eq this.res.carrier "stream")}}Generator[{{fmtType this.res}}, None, None]{{else}}{{#if this.res.path}}{{fmtType this.res (to_pascal_case this.name)}}{{else if (or (eq this.res.type "obj") (eq this.res.type "seq") (eq this.res.type "map"))}}{{>dtoName val=(fmtClass (add this.name "Res"))}}{{else}}{{fmtType this.res}}{{/if}}{{/if}}{{#if this.res.meta}}{{#if (filterNonconst this.res.meta)}}, {{to_pascal_case this.name}}Headers]{{/if}}{{/if}}{{else}}None{{/if}}: - raise NotImplementedError + {{#if ../../autoImpl}}{{#if this.res}}return {{#if (eq this.res.type "seq")}}[{{>dtoName val=(fmtClass (add this.name "ResItem"))}}.get()]{{else if (eq this.res.type "map")}}{ {{>dtoName val=(fmtClass (add this.name "ResKey"))}}.get(): {{>dtoName val=(fmtClass (add this.name "ResVal"))}}.get() }{{else}}{{fmtType this.res}}.get(){{/if}}{{else}}pass{{/if}}{{else}}raise NotImplementedError{{/if}} {{/each}}{{/each}} diff --git a/src/lib/gen/python/server/templates/stubImpl.hbs b/src/lib/gen/python/server/templates/stubImpl.hbs new file mode 100644 index 0000000..774f44b --- /dev/null +++ b/src/lib/gen/python/server/templates/stubImpl.hbs @@ -0,0 +1,3 @@ +{{#if (and val.path (not (eq val.type "alias")))}}{{#with (resolve val)}}{{#if (eq this.type "obj")}}{{#if this.adt}}{{>dtoName val=(fmtClass (fmtName ../val.path))}}Base{{else}}{{>dtoName val=(fmtClass ../val.path)}}{{/if}}.get(){{else}}{{>stubImpl val=this indent=indent}}{{/if}}{{/with}}{{else if (or (eq val.type "obj") val.vars)}}{{key}}({{#each val.vars}} + {{../indent}} {{fmtName @key}}={{>stubImpl val=this key=(fmtClass @key) indent=(add ../indent " ") prefix=../key}},{{/each}} +{{indent}}){{else if (eq val.type "map")}}{ {{#if val.key}}{{#if (and prefix (not val.key.path))}}{{prefix}}.{{/if}}{{>stubImpl val=val.key key=(add key "Key") indent=indent}}{{else}}{{>stubImpl val="str" key=(add key "Key") indent=indent}}{{/if}}: {{#if val.val}}{{#if (and prefix (not val.val.path))}}{{prefix}}.{{/if}}{{>stubImpl val=val.val key=(add key "Val") indent=indent}}{{else}}{{>stubImpl val="str" key=(add key "Val") indent=indent}}{{/if}} }{{else if (eq val.type "seq")}}[{{#if (eq val.item.type "obj")}}{{#if prefix}}{{prefix}}.{{/if}}{{add key "Item"}}.get(){{else}}{{>stubImpl val=val.item indent=indent prefix=null}}{{/if}}]{{else if val.mix}}{{unimplemented}}{{else}}{{stubImpl val indent=indent}}{{/if}} \ No newline at end of file diff --git a/src/lib/gen/template/proc.rs b/src/lib/gen/template/proc.rs index 88f0f3e..c8f3914 100644 --- a/src/lib/gen/template/proc.rs +++ b/src/lib/gen/template/proc.rs @@ -1,8 +1,11 @@ -use crate::lib::{ - context::Context, - def::{Def, Int, Obj, Str}, - desc::Desc, - ext::Ext, +use crate::{ + gen::{gen::Gen, lang::stub_impl}, + lib::{ + context::Context, + def::{Def, Int, Obj, Str}, + desc::Desc, + ext::Ext, + }, }; use handlebars::{ Handlebars, Helper, HelperDef, JsonRender, RenderContext, RenderError, ScopedJson, @@ -56,6 +59,40 @@ impl HelperDef for Resolve { } } +#[derive(Clone)] +pub(crate) struct StubImpl { + pub gen: Box, + pub context: Context, +} + +impl HelperDef for StubImpl { + fn call_inner<'reg: 'rc, 'rc>( + &self, + h: &Helper<'rc>, + _: &'reg Handlebars<'reg>, + _: &'rc handlebars::Context, + _: &mut RenderContext<'reg, 'rc>, + ) -> Result, RenderError> { + let desc: Desc = serde_json::from_value(h.param(0).unwrap().value().clone()) + .unwrap_or_else(|_| { + let r#type: String = + serde_json::from_value(h.param(0).unwrap().value().clone()).unwrap(); + Desc::Def(match r#type.as_str() { + "bool" => Def::Bool(Default::default()), + "dec" => Def::Dec(Default::default()), + "int" => Def::Int(Default::default()), + "str" => Def::Str(Default::default()), + _ => unreachable!(), + }) + }); + Ok( + serde_json::to_value(stub_impl(&self.gen.lang(), &desc, &self.context)) + .unwrap() + .into(), + ) + } +} + #[derive(Clone)] pub(crate) struct TypeArgs;