Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python codegen #2295

Draft
wants to merge 3 commits into
base: aw/new-schema
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
1 change: 1 addition & 0 deletions packages/cw-schema-codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod go;
pub mod python;
pub mod rust;
pub mod typescript;
3 changes: 2 additions & 1 deletion packages/cw-schema-codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ where
Language::Typescript => {
cw_schema_codegen::typescript::process_node(output, schema, node)
}
Language::Go | Language::Python => todo!(),
Language::Python => cw_schema_codegen::python::process_node(output, schema, node),
Language::Go => todo!(),
}
})?;

Expand Down
140 changes: 140 additions & 0 deletions packages/cw-schema-codegen/src/python/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use self::template::{
EnumTemplate, EnumVariantTemplate, FieldTemplate, StructTemplate, TypeTemplate,
};
use heck::ToPascalCase;
use std::{borrow::Cow, io};

pub mod template;

fn expand_node_name<'a>(
schema: &'a cw_schema::SchemaV1,
node: &'a cw_schema::Node,
) -> Cow<'a, str> {
match node.value {
cw_schema::NodeType::Array { items } => {
let items = &schema.definitions[items];
format!("{}[]", expand_node_name(schema, items)).into()
}
cw_schema::NodeType::Float => "number".into(),
cw_schema::NodeType::Double => "number".into(),
cw_schema::NodeType::Boolean => "boolean".into(),
cw_schema::NodeType::String => "string".into(),
cw_schema::NodeType::Integer { .. } => "string".into(),
cw_schema::NodeType::Binary => "Uint8Array".into(),
cw_schema::NodeType::Optional { inner } => {
let inner = &schema.definitions[inner];
format!("{} | null", expand_node_name(schema, inner)).into()
}
cw_schema::NodeType::Struct(..) => node.name.as_ref().into(),
cw_schema::NodeType::Tuple { ref items } => {
let items = items
.iter()
.map(|item| expand_node_name(schema, &schema.definitions[*item]))
.collect::<Vec<_>>()
.join(", ");

format!("[{}]", items).into()
}
cw_schema::NodeType::Enum { .. } => node.name.as_ref().into(),

cw_schema::NodeType::Decimal { .. } => "string".into(),
cw_schema::NodeType::Address => "string".into(),
cw_schema::NodeType::Checksum => todo!(),
cw_schema::NodeType::HexBinary => todo!(),
cw_schema::NodeType::Timestamp => todo!(),
cw_schema::NodeType::Unit => Cow::Borrowed("void"),
_ => todo!()
}
}

fn prepare_docs(desc: Option<&str>) -> Cow<'_, [Cow<'_, str>]> {
desc.map(|desc| desc.lines().map(Into::into).collect())
.unwrap_or(Cow::Borrowed(&[]))
}

pub fn process_node<O>(
output: &mut O,
schema: &cw_schema::SchemaV1,
node: &cw_schema::Node,
) -> io::Result<()>
where
O: io::Write,
{
match node.value {
cw_schema::NodeType::Struct(ref sty) => {
let structt = StructTemplate {
name: node.name.clone(),
docs: prepare_docs(node.description.as_deref()),
ty: match sty {
cw_schema::StructType::Unit => TypeTemplate::Unit,
cw_schema::StructType::Named { ref properties } => TypeTemplate::Named {
fields: properties
.iter()
.map(|(name, prop)| FieldTemplate {
name: Cow::Borrowed(name),
docs: prepare_docs(prop.description.as_deref()),
ty: expand_node_name(schema, &schema.definitions[prop.value]),
})
.collect(),
},
cw_schema::StructType::Tuple { ref items } => TypeTemplate::Tuple(
items
.iter()
.map(|item| expand_node_name(schema, &schema.definitions[*item]))
.collect(),
),
_ => todo!()
},
};

writeln!(output, "{structt}")?;
}
cw_schema::NodeType::Enum { ref cases, .. } => {
let enumm = EnumTemplate {
name: node.name.clone(),
docs: prepare_docs(node.description.as_deref()),
variants: cases
.iter()
.map(|(name, case)| EnumVariantTemplate {
name: name.clone(),
docs: prepare_docs(case.description.as_deref()),
ty: match case.value {
cw_schema::EnumValue::Unit => TypeTemplate::Unit,
cw_schema::EnumValue::Tuple { ref items } => {
let items = items
.iter()
.map(|item| {
expand_node_name(schema, &schema.definitions[*item])
})
.collect();

TypeTemplate::Tuple(items)
}
cw_schema::EnumValue::Named { ref properties, .. } => {
TypeTemplate::Named {
fields: properties
.iter()
.map(|(name, prop)| FieldTemplate {
name: Cow::Borrowed(name),
docs: prepare_docs(prop.description.as_deref()),
ty: expand_node_name(
schema,
&schema.definitions[prop.value],
),
})
.collect(),
}
}
_ => todo!()
},
})
.collect(),
};

writeln!(output, "{enumm}")?;
}
_ => (),
}

Ok(())
}
Loading
Loading