From 133685ecd69d01aebab5cdb15e53bbe959325686 Mon Sep 17 00:00:00 2001 From: Tomasz Kulik Date: Fri, 8 Nov 2024 08:11:23 +0100 Subject: [PATCH] feat: Add Python codegen --- Cargo.toml | 2 +- .../cw-schema-codegen/playground/.gitignore | 1 + .../cw-schema-codegen/playground/Cargo.lock | 96 +++++++++++++++++++ .../cw-schema-codegen/playground/Cargo.toml | 11 +++ .../playground/playground.py | 91 ++++++++++++++++++ .../cw-schema-codegen/playground/src/main.rs | 35 +++++++ packages/cw-schema-codegen/playground/test.sh | 1 + packages/cw-schema-codegen/src/python/mod.rs | 3 + .../templates/python/enum.tpl.py | 50 +++++----- .../cw-schema-codegen/tests/python_tpl.rs | 29 ++++++ .../snapshots/python_tpl__simple_enum.snap | 26 +++++ 11 files changed, 318 insertions(+), 27 deletions(-) create mode 100644 packages/cw-schema-codegen/playground/.gitignore create mode 100644 packages/cw-schema-codegen/playground/Cargo.lock create mode 100644 packages/cw-schema-codegen/playground/Cargo.toml create mode 100644 packages/cw-schema-codegen/playground/playground.py create mode 100644 packages/cw-schema-codegen/playground/src/main.rs create mode 100755 packages/cw-schema-codegen/playground/test.sh create mode 100644 packages/cw-schema-codegen/tests/python_tpl.rs create mode 100644 packages/cw-schema-codegen/tests/snapshots/python_tpl__simple_enum.snap diff --git a/Cargo.toml b/Cargo.toml index c94fa1046..c06f9b080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/packages/cw-schema-codegen/playground/.gitignore b/packages/cw-schema-codegen/playground/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/packages/cw-schema-codegen/playground/.gitignore @@ -0,0 +1 @@ +/target diff --git a/packages/cw-schema-codegen/playground/Cargo.lock b/packages/cw-schema-codegen/playground/Cargo.lock new file mode 100644 index 000000000..867ff0bae --- /dev/null +++ b/packages/cw-schema-codegen/playground/Cargo.lock @@ -0,0 +1,96 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serialization" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/packages/cw-schema-codegen/playground/Cargo.toml b/packages/cw-schema-codegen/playground/Cargo.toml new file mode 100644 index 000000000..a5aa7ba8f --- /dev/null +++ b/packages/cw-schema-codegen/playground/Cargo.toml @@ -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" diff --git a/packages/cw-schema-codegen/playground/playground.py b/packages/cw-schema-codegen/playground/playground.py new file mode 100644 index 000000000..2286e2d9e --- /dev/null +++ b/packages/cw-schema-codegen/playground/playground.py @@ -0,0 +1,91 @@ +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 EnumVariants(Enum): + Field1 = '"Field1"' + + _EnumVariant: Optional[EnumVariants] = 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 get_variant(self): + self.Tuple + + def deserialize(json): + if not ":" in json: + if json == '"Field1"': + return SomeEnum(_EnumVariant=SomeEnum.EnumVariants.Field1) + else: + return SomeEnum.from_json(json) + + def serialize(self): + if self._EnumVariant is not None: + return self._EnumVariant.value + else: + return SomeEnum.to_json(self) + +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) + + + + +# class Dupa: +# def __init__(self, field, variant_name): +# self.__value = field +# self.__variant_name = variant_name + +# def variant_name(self): +# return self.__variant_name + +# def Field1(x: tuple[int, int]): +# return Dupa(x, "Field1") + +# def get_Field1(self) -> tuple[int, int]: +# if self.__variant_name != "Field1": +# raise Exception(f"Improper anyOf variant. Expected: {self.__variant_name}") +# return self.__value + +# def Field2(x: str): +# return Dupa(x, "Field2") + +# def get_Field2(self) -> str: +# if self.__variant_name != "Field2": +# raise Exception(f"Improper anyOf variant. Expected: {self.__variant_name}") +# return self.__value + + +# x = Dupa.Field1((12,12)) +# print(x.variant_name()) +# print(x.get_Field2()) diff --git a/packages/cw-schema-codegen/playground/src/main.rs b/packages/cw-schema-codegen/playground/src/main.rs new file mode 100644 index 000000000..aa2cd5d47 --- /dev/null +++ b/packages/cw-schema-codegen/playground/src/main.rs @@ -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), + Field5 { a: Box }, +} + + +#[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(); + } +} diff --git a/packages/cw-schema-codegen/playground/test.sh b/packages/cw-schema-codegen/playground/test.sh new file mode 100755 index 000000000..921d0e29b --- /dev/null +++ b/packages/cw-schema-codegen/playground/test.sh @@ -0,0 +1 @@ +cargo run | python playground.py | cargo run --features "deserialize" \ No newline at end of file diff --git a/packages/cw-schema-codegen/src/python/mod.rs b/packages/cw-schema-codegen/src/python/mod.rs index 74f3d9e00..da918a8b0 100644 --- a/packages/cw-schema-codegen/src/python/mod.rs +++ b/packages/cw-schema-codegen/src/python/mod.rs @@ -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!() } } @@ -82,6 +83,7 @@ where .map(|item| expand_node_name(schema, &schema.definitions[*item])) .collect(), ), + _ => todo!() }, }; @@ -123,6 +125,7 @@ where .collect(), } } + _ => todo!() }, }) .collect(), diff --git a/packages/cw-schema-codegen/templates/python/enum.tpl.py b/packages/cw-schema-codegen/templates/python/enum.tpl.py index 21ee91261..16422ad73 100644 --- a/packages/cw-schema-codegen/templates/python/enum.tpl.py +++ b/packages/cw-schema-codegen/templates/python/enum.tpl.py @@ -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 %} \ No newline at end of file diff --git a/packages/cw-schema-codegen/tests/python_tpl.rs b/packages/cw-schema-codegen/tests/python_tpl.rs new file mode 100644 index 000000000..410995922 --- /dev/null +++ b/packages/cw-schema-codegen/tests/python_tpl.rs @@ -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); +} diff --git a/packages/cw-schema-codegen/tests/snapshots/python_tpl__simple_enum.snap b/packages/cw-schema-codegen/tests/snapshots/python_tpl__simple_enum.snap new file mode 100644 index 000000000..e5806536e --- /dev/null +++ b/packages/cw-schema-codegen/tests/snapshots/python_tpl__simple_enum.snap @@ -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" = {} }