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 19, 2024
1 parent cc10b12 commit 8da894e
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 27 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"
81 changes: 81 additions & 0 deletions packages/cw-schema-codegen/playground/playground.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from typing import Optional, Iterable
from enum import Enum
import sys


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


@dataclass_json
@dataclass
class SomeEnumField3:
a: str
b: int

@dataclass_json
@dataclass
class SomeEnumField5:
a: Iterable['SomeEnum']

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

@dataclass_json
@dataclass
class SomeEnum:
class VariantIndicator:
pass

Field1: Optional[VariantIndicator] = enum_field()
Field2: Optional[tuple[int, int]] = enum_field()
Field3: Optional[SomeEnumField3] = enum_field()
Field4: Optional[Iterable['SomeEnum']] = enum_field()
Field5: Optional[SomeEnumField5] = 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
50 changes: 24 additions & 26 deletions packages/cw-schema-codegen/templates/python/enum.tpl.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
# This code is @generated by cw-schema-codegen. Do not modify this manually.

/**
{% for doc in docs %}
* {{ doc }}
{% endfor %}
*/
class {{ name }}:
'''{% for doc in docs %}
{{ doc }}
{% endfor %}'''

type {{ name }} =
{% for variant in variants %}
|
def __init__(self, value, variant_name):
self.__value = value
self.__variant_name = variant_name

/**
{% for doc in variant.docs %}
* {{ doc }}
{% endfor %}
*/
def variant_name(self):
return self.__variant_name

{% for variant in variants %}
'''{% for doc in variant.docs %}
{{ doc }}
{% endfor %}'''
{% match variant.ty %}
{% when TypeTemplate::Unit %}
{ "{{ variant.name }}": {} }
{{ variant.name }} = None
{% when TypeTemplate::Tuple with (types) %}
{ "{{ variant.name }}": [{{ types|join(", ") }}] }
def {{ variant.name }}(value: tuple[{{ types|join(", ") }}]):
{{ name }} (value, "{{ name }}")
{% when TypeTemplate::Named with { fields } %}
{ "{{ variant.name }}": {
def {{ variant.name }}(value: tuple[{{ types|join(", ") }}]):
{{ name }} (value, "{{ name }}")

{{ variant.name }} = {
{% for field in fields %}
/**
{% for doc in field.docs %}
* {{ doc }}
# {{ doc }}
{% endfor %}
*/

{{ field.name }}: {{ field.ty }};
"{{ field.name }}": {{ field.ty }},
{% endfor %}
} }
}
{% endmatch %}
{% endfor %}
;

export { {{ name }} };
{% endfor %}
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
source: packages/cw-schema-codegen/tests/python_tpl.rs
expression: rendered
snapshot_kind: text
---
# This code is @generated by cw-schema-codegen. Do not modify this manually.

/**
* Simple enum
*/
class Simple (Enum):


# One variant


{ "One" = {} }



# Two variant


{ "Two" = {} }

0 comments on commit 8da894e

Please sign in to comment.