Skip to content

Commit

Permalink
feat: Add Python codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
kulikthebird committed Nov 21, 2024
1 parent cc10b12 commit 1fb7d03
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
members = ["packages/*"]
exclude = ["contracts"]
exclude = ["contracts", "packages/cw-schema-codegen/playground"]

# Resolver has to be set explicitly in workspaces
# due to https://github.com/rust-lang/cargo/issues/9956
Expand Down
1 change: 1 addition & 0 deletions packages/cw-schema-codegen/playground/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
96 changes: 96 additions & 0 deletions packages/cw-schema-codegen/playground/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/cw-schema-codegen/playground/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "serialization"
version = "0.1.0"
edition = "2021"

[features]
deserialize = []

[dependencies]
serde = { version = "1.0.215", features = ["derive", "serde_derive"] }
serde_json = "1.0.133"
76 changes: 76 additions & 0 deletions packages/cw-schema-codegen/playground/playground.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from typing import Optional, Iterable
import sys


# TODO tkulik: try to get rid of the `dataclasses_json` dependency


enum_field = lambda: field(default=None, metadata=config(exclude=lambda x: x is None))

@dataclass_json
@dataclass
class SomeEnum:
class VariantIndicator:
pass

class Field3Type:
a: str
b: int

class Field5Type:
a: Iterable['SomeEnum']

Field1: Optional[VariantIndicator] = enum_field()
Field2: Optional[tuple[int, int]] = enum_field()
Field3: Optional[Field3Type] = enum_field()
Field4: Optional[Iterable['SomeEnum']] = enum_field()
Field5: Optional[Field5Type] = enum_field()

def deserialize(json):
if not ":" in json:
if json == '"Field1"':
return SomeEnum(Field1=SomeEnum.VariantIndicator())
else:
raise Exception(f"Deserialization error, undefined variant: {json}")
else:
return SomeEnum.from_json(json)

def serialize(self):
if self.Field1 is not None:
return '"Field1"'
else:
return SomeEnum.to_json(self)


###
### TESTS:
###

for input in sys.stdin:
input = input.rstrip()
try:
deserialized = SomeEnum.deserialize(input)
except:
raise(Exception(f"This json can't be deserialized: {input}"))
serialized = deserialized.serialize()
print(serialized)


# def handle_msg(json):
# a = SomeEnum.deserialize(json)
# if a.Field1 is not None:
# print("SomeEnum::Field1")
# elif a.Field2 is not None:
# print(a.Field2[0])
# print(a.Field2[1])
# elif a.Field3 is not None:
# print(a.Field3)
# elif a.Field4 is not None:
# print(a.Field4)
# elif a.Field5 is not None:
# print(a.Field5)

# handle_msg('"Field1"')
# handle_msg('{"Field2": [10, 12]}')
35 changes: 35 additions & 0 deletions packages/cw-schema-codegen/playground/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

use serde::{Deserialize, Serialize};


#[derive(Serialize, Deserialize)]
pub enum SomeEnum {
Field1,
Field2(u32, u32),
Field3 {
a: String,
b: u32
},
Field4(Box<SomeEnum>),
Field5 { a: Box<SomeEnum> },
}


#[cfg(not(feature = "deserialize"))]
fn main() {
println!("{}", serde_json::to_string(&SomeEnum::Field1).unwrap());
println!("{}", serde_json::to_string(&SomeEnum::Field2(10, 23)).unwrap());
println!("{}", serde_json::to_string(&SomeEnum::Field3 {a: "sdf".to_string(), b: 12}).unwrap());
println!("{}", serde_json::to_string(&SomeEnum::Field4(Box::new(SomeEnum::Field1))).unwrap());
println!("{}", serde_json::to_string(&SomeEnum::Field5 { a: Box::new(SomeEnum::Field1) }).unwrap());
}

#[cfg(feature = "deserialize")]
fn main() {
use std::io::BufRead;
for line in std::io::BufReader::new(std::io::stdin()).lines() {
let line = line.unwrap();
println!("{line}");
let _: SomeEnum = serde_json::from_str(&line).unwrap();
}
}
1 change: 1 addition & 0 deletions packages/cw-schema-codegen/playground/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cargo run | python playground.py | cargo run --features "deserialize"
3 changes: 3 additions & 0 deletions packages/cw-schema-codegen/src/python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fn expand_node_name<'a>(
cw_schema::NodeType::HexBinary => todo!(),
cw_schema::NodeType::Timestamp => todo!(),
cw_schema::NodeType::Unit => Cow::Borrowed("void"),
_ => todo!()
}
}

Expand Down Expand Up @@ -82,6 +83,7 @@ where
.map(|item| expand_node_name(schema, &schema.definitions[*item]))
.collect(),
),
_ => todo!()
},
};

Expand Down Expand Up @@ -123,6 +125,7 @@ where
.collect(),
}
}
_ => todo!()
},
})
.collect(),
Expand Down
60 changes: 32 additions & 28 deletions packages/cw-schema-codegen/templates/python/enum.tpl.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
# This code is @generated by cw-schema-codegen. Do not modify this manually.
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from typing import Optional, Iterable

/**
{% for doc in docs %}
* {{ doc }}
{% endfor %}
*/
enum_field = lambda: field(default=None, metadata=config(exclude=lambda x: x is None))

type {{ name }} =
{% for variant in variants %}
|
@dataclass_json
@dataclass
class {{ name }}:
'''{% for doc in docs %}
{{ doc }}
{% endfor %}'''

class VariantIndicator:
'''
This structure is an indicator of the simple enum variant that is currently contained
in this enum structure. It's used only for the enum variants that does not contain
any inner structure. It's constructed automatically and it's not intend to be manually
used by the user.
'''
pass

/**
{% for doc in variant.docs %}
* {{ doc }}
{% endfor %}
*/

{% for variant in variants %}
'''{% for doc in variant.docs %}
{{ doc }}
{% endfor %}'''
{% match variant.ty %}
{% when TypeTemplate::Unit %}
{ "{{ variant.name }}": {} }
{{ variant.name }}: Optional[VariantIndicator] = enum_field()
{% when TypeTemplate::Tuple with (types) %}
{ "{{ variant.name }}": [{{ types|join(", ") }}] }
{{ variant.name }}: Optional[tuple[{{ types|join(", ") }}]] = enum_field()
{% when TypeTemplate::Named with { fields } %}
{ "{{ variant.name }}": {
class {{ variant.name }}Type:
{% for field in fields %}
/**
{% for doc in field.docs %}
* {{ doc }}
{% endfor %}
*/

{{ field.name }}: {{ field.ty }};
'''{% for doc in field.docs %}
# {{ doc }}
{% endfor %}'''
{{ field.name }}: {{ field.ty }}
{% endfor %}
} }
{{ variant.name }}: Optional[{{ variant.name }}Type] = enum_field()
{% endmatch %}
{% endfor %}
;

export { {{ name }} };
{% endfor %}
29 changes: 27 additions & 2 deletions packages/cw-schema-codegen/templates/python/struct.tpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@
{% endfor %}
}
{% endmatch %}
;

export { {{ name }} };


# This code is @generated by cw-schema-codegen. Do not modify this manually.
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from typing import Optional, Iterable

@dataclass_json
@dataclass
class {{ name }}:
'''{% for doc in docs %}
{{ doc }}
{% endfor %}'''

{% match ty %}
{% when TypeTemplate::Unit %}
pass
{% when TypeTemplate::Tuple with (types) %}
{{ variant.name }}: tuple[{{ types|join(", ") }}]
{% when TypeTemplate::Named with { fields } %}
{% for field in fields %}
'''{% for doc in field.docs %}
# {{ doc }}
{% endfor %}'''
{{ field.name }}: {{ field.ty }}
{% endfor %}
{% endmatch %}
29 changes: 29 additions & 0 deletions packages/cw-schema-codegen/tests/python_tpl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::borrow::Cow;

use askama::Template;
use cw_schema_codegen::python::template::{
EnumTemplate, EnumVariantTemplate, FieldTemplate, StructTemplate, TypeTemplate,
};

#[test]
fn simple_enum() {
let tpl = EnumTemplate {
name: Cow::Borrowed("Simple"),
docs: Cow::Borrowed(&[Cow::Borrowed("Simple enum")]),
variants: Cow::Borrowed(&[
EnumVariantTemplate {
name: Cow::Borrowed("One"),
docs: Cow::Borrowed(&[Cow::Borrowed("One variant")]),
ty: TypeTemplate::Unit,
},
EnumVariantTemplate {
name: Cow::Borrowed("Two"),
docs: Cow::Borrowed(&[Cow::Borrowed("Two variant")]),
ty: TypeTemplate::Unit,
},
]),
};

let rendered = tpl.render().unwrap();
insta::assert_snapshot!(rendered);
}
Loading

0 comments on commit 1fb7d03

Please sign in to comment.