...
This commit is contained in:
		
							
								
								
									
										0
									
								
								_archive/openrpc/generator/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								_archive/openrpc/generator/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										77
									
								
								_archive/openrpc/generator/client/generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								_archive/openrpc/generator/client/generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
from typing import Dict, List, Optional, Union
 | 
			
		||||
from urllib.parse import urlparse
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.code.lang_code_generator import LangCodeGenerator
 | 
			
		||||
from heroserver.openrpc.generator.model.model_generator import ModelGenerator
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.model.common import (
 | 
			
		||||
    ContentDescriptorObject,
 | 
			
		||||
    ReferenceObject,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientGenerator:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        spec: OpenRPCSpec,
 | 
			
		||||
        lang_code_generator: LangCodeGenerator,
 | 
			
		||||
        output_file: str,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.spec = spec
 | 
			
		||||
        self.model_generator = ModelGenerator(spec, lang_code_generator)
 | 
			
		||||
        self.lang_code_generator = lang_code_generator
 | 
			
		||||
        self.output_file = output_file
 | 
			
		||||
 | 
			
		||||
    def generate_client(self):
 | 
			
		||||
        code_pre = self.lang_code_generator.generate_imports()
 | 
			
		||||
        code_models = self.model_generator.generate_models()
 | 
			
		||||
        code_methods = self.generate_methods()
 | 
			
		||||
 | 
			
		||||
        # Write the generated code to a file
 | 
			
		||||
        with open(self.output_file, "w") as file:
 | 
			
		||||
            file.write(code_pre)
 | 
			
		||||
            file.write("\n")
 | 
			
		||||
            file.write(code_models)
 | 
			
		||||
            file.write("\n")
 | 
			
		||||
            file.write(code_methods)
 | 
			
		||||
 | 
			
		||||
        print(f"Generated API code has been written to {self.output_file}")
 | 
			
		||||
 | 
			
		||||
    def generate_methods(self):
 | 
			
		||||
        servers = self.spec.servers
 | 
			
		||||
        base_url = "http://localhost:8000"
 | 
			
		||||
        if servers:
 | 
			
		||||
            base_url = servers[0].url
 | 
			
		||||
 | 
			
		||||
        url = urlparse(base_url)
 | 
			
		||||
        methods = []
 | 
			
		||||
        for method_spec in self.spec.methods:
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method_spec.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(
 | 
			
		||||
                    ["methods", method_spec.name, "params", param.name],
 | 
			
		||||
                    param.schema,
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            return_type = self.method_result_return_type(["methods", method_spec.name, "result"], method_spec.result)
 | 
			
		||||
            methods.append(self.lang_code_generator.generate_method(method_spec, url, params, return_type))
 | 
			
		||||
 | 
			
		||||
        return "\n\n".join(methods)
 | 
			
		||||
 | 
			
		||||
    def method_result_return_type(
 | 
			
		||||
        self,
 | 
			
		||||
        path: List[str],
 | 
			
		||||
        method_result: Optional[Union[ContentDescriptorObject, ReferenceObject]],
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        if not method_result:
 | 
			
		||||
            type_name = ""
 | 
			
		||||
 | 
			
		||||
        if isinstance(method_result, ContentDescriptorObject):
 | 
			
		||||
            schema = method_result.schema
 | 
			
		||||
            type_name = self.model_generator.jsonschema_to_type(path, schema)
 | 
			
		||||
 | 
			
		||||
        elif isinstance(method_result, ReferenceObject):
 | 
			
		||||
            type_name = self.model_generator.jsonschema_to_type(path, method_result)
 | 
			
		||||
 | 
			
		||||
        return type_name
 | 
			
		||||
							
								
								
									
										177
									
								
								_archive/openrpc/generator/code/golang/golang_code_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								_archive/openrpc/generator/code/golang/golang_code_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
from typing import Any, Dict, List
 | 
			
		||||
from urllib.parse import ParseResult
 | 
			
		||||
 | 
			
		||||
import inflect
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
from heroserver.openrpc.generator.lang_code_generator import LangCodeGenerator, PropertyInfo
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.model.common import (
 | 
			
		||||
    ReferenceObject,
 | 
			
		||||
    SchemaObject,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.methods import MethodObject
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import (
 | 
			
		||||
    OpenRPCSpec,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
inflector = inflect.engine()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GolangCodeGenerator(LangCodeGenerator):
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
        self.struct_template = env.get_template("templates/struct.jinja")
 | 
			
		||||
        self.methods_template = env.get_template("templates/methods.jinja")
 | 
			
		||||
        self.pre_template = env.get_template("templates/pre.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate_imports(self) -> str:
 | 
			
		||||
        return self.pre_template.render(
 | 
			
		||||
            package_name="rpcclient",
 | 
			
		||||
            imports=[
 | 
			
		||||
                "net/http",
 | 
			
		||||
                "github.com/mitchellh/mapstructure",
 | 
			
		||||
                "encoding/json",
 | 
			
		||||
                "bytes",
 | 
			
		||||
                "fmt",
 | 
			
		||||
                "io",
 | 
			
		||||
            ],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def generate_object(
 | 
			
		||||
        self,
 | 
			
		||||
        type_name: str,
 | 
			
		||||
        properties: Dict[str, PropertyInfo],
 | 
			
		||||
    ):
 | 
			
		||||
        return self.struct_template.render(generator=self, type_name=type_name, properties=properties)
 | 
			
		||||
 | 
			
		||||
    def generate_method(
 | 
			
		||||
        self,
 | 
			
		||||
        method_spec: MethodObject,
 | 
			
		||||
        url: ParseResult,
 | 
			
		||||
        params: Dict[str, str],
 | 
			
		||||
        return_type: str,
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        function_name = self.get_camel_case_name(method_spec.name)
 | 
			
		||||
        method_name = method_spec.name
 | 
			
		||||
        method_result = self.type_to_method_result(return_type)
 | 
			
		||||
        method_description = ""
 | 
			
		||||
        if method_spec.description:
 | 
			
		||||
            method_description = method_spec.description.replace("'", " ")
 | 
			
		||||
 | 
			
		||||
        method_example = ""
 | 
			
		||||
        if method_spec.examples and len(method_spec.examples) > 0:
 | 
			
		||||
            method_example = json.dumps(method_spec.examples[0], indent=4)
 | 
			
		||||
 | 
			
		||||
        method_code = self.methods_template.render(
 | 
			
		||||
            generator=self,
 | 
			
		||||
            url=url.geturl(),
 | 
			
		||||
            function_name=function_name,
 | 
			
		||||
            method_name=method_name,
 | 
			
		||||
            method_params=params,
 | 
			
		||||
            method_result=method_result,
 | 
			
		||||
            return_type=return_type,
 | 
			
		||||
            method_description=method_description,
 | 
			
		||||
            method_example=method_example,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return method_code
 | 
			
		||||
 | 
			
		||||
    def string_primitive(self) -> str:
 | 
			
		||||
        return "string"
 | 
			
		||||
 | 
			
		||||
    def integer_primitive(self) -> str:
 | 
			
		||||
        return "int64"
 | 
			
		||||
 | 
			
		||||
    def number_primitive(self) -> str:
 | 
			
		||||
        return "float64"
 | 
			
		||||
 | 
			
		||||
    def null_primitive(self) -> str:
 | 
			
		||||
        return "nil"
 | 
			
		||||
 | 
			
		||||
    def bool_primitive(self) -> str:
 | 
			
		||||
        return "bool"
 | 
			
		||||
 | 
			
		||||
    def array_of_type(self, type_name: str) -> str:
 | 
			
		||||
        return f"[]{type_name}"
 | 
			
		||||
 | 
			
		||||
    def generate_multitype(self, types: List[str]) -> str:
 | 
			
		||||
        if len(types) > 2:
 | 
			
		||||
            raise Exception("only a type and null are supported with anyOf/allOf keyword")
 | 
			
		||||
 | 
			
		||||
        if len(types) == 1:
 | 
			
		||||
            return types[0]
 | 
			
		||||
 | 
			
		||||
        if types[0] == "nil":
 | 
			
		||||
            return f"*{types[1]}"
 | 
			
		||||
        if types[1] == "nil":
 | 
			
		||||
            return f"*{types[0]}"
 | 
			
		||||
 | 
			
		||||
        raise Exception("only a type and null are supported with anyOf/allOf keyword")
 | 
			
		||||
 | 
			
		||||
    def encapsulate_types(self, path: List[str], types: List[SchemaObject | ReferenceObject]) -> str:
 | 
			
		||||
        raise Exception("no support for allOf keyword")
 | 
			
		||||
 | 
			
		||||
    def generate_enum(self, enum: List[Any], type_name: str) -> str:
 | 
			
		||||
        if all(isinstance(elem, str) for elem in enum):
 | 
			
		||||
            return self.string_primitive()
 | 
			
		||||
 | 
			
		||||
        elif all(isinstance(elem, int) for elem in enum):
 | 
			
		||||
            return self.integer_primitive()
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(f"failed to generate enum code for: {enum}")
 | 
			
		||||
 | 
			
		||||
    def type_to_method_result(self, type_name: str) -> str:
 | 
			
		||||
        method_result = "error"
 | 
			
		||||
        if len(type_name) > 0 and type_name != "nil":
 | 
			
		||||
            method_result = f"({type_name}, error)"
 | 
			
		||||
 | 
			
		||||
        return method_result
 | 
			
		||||
 | 
			
		||||
    def is_primitive(self, type: str) -> bool:
 | 
			
		||||
        return type in ["int64", "float64", "int", "bool", "string"]
 | 
			
		||||
 | 
			
		||||
    def is_array(self, type: str) -> bool:
 | 
			
		||||
        return type.startswith("[]")
 | 
			
		||||
 | 
			
		||||
    def get_method_params(self, method_params: Dict[str, str]) -> str:
 | 
			
		||||
        return ", ".join([f"{param_name} {param_type}" for param_name, param_type in method_params.items()])
 | 
			
		||||
 | 
			
		||||
    def get_camel_case_name(self, method_name: str) -> str:
 | 
			
		||||
        return "".join([item.title() for item in method_name.split("_")])
 | 
			
		||||
 | 
			
		||||
    def get_default_return_with_error(self, return_type: str, error_statement: str) -> str:
 | 
			
		||||
        if return_type == "nil":
 | 
			
		||||
            return error_statement
 | 
			
		||||
 | 
			
		||||
        if return_type == "string":
 | 
			
		||||
            return f'"", {error_statement}'
 | 
			
		||||
 | 
			
		||||
        if return_type == "bool":
 | 
			
		||||
            return f"false, {error_statement}"
 | 
			
		||||
 | 
			
		||||
        if return_type == "float64" or return_type == "int64":
 | 
			
		||||
            return f"0, {error_statement}"
 | 
			
		||||
 | 
			
		||||
        return f"{return_type}{{}}, {error_statement}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# main()
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    from heroserver.openrpc.generator.generator import ClientGenerator
 | 
			
		||||
    from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
    data = parser(path="/root/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs/storymanager")
 | 
			
		||||
 | 
			
		||||
    spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
    golang_code_generator = GolangCodeGenerator()
 | 
			
		||||
    generator = ClientGenerator(
 | 
			
		||||
        spec_object,
 | 
			
		||||
        golang_code_generator,
 | 
			
		||||
        "/tmp/go_client_new.go",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    generator.generate_client()
 | 
			
		||||
@@ -0,0 +1,92 @@
 | 
			
		||||
{% if method_example -%}
 | 
			
		||||
/*
 | 
			
		||||
Example:
 | 
			
		||||
{{ method_example }}
 | 
			
		||||
*/
 | 
			
		||||
{% endif -%}
 | 
			
		||||
 | 
			
		||||
{% if method_description -%}
 | 
			
		||||
/*
 | 
			
		||||
{{ method_description }}
 | 
			
		||||
*/
 | 
			
		||||
{% endif -%}
 | 
			
		||||
func {{ function_name }}({{ generator.get_method_params(method_params) }}) {{ method_result }} {
 | 
			
		||||
    params := map[string]interface{}{}
 | 
			
		||||
    {%- for param_name, param_type in method_params.items() %}
 | 
			
		||||
    params["{{param_name}}"] = {{param_name}}
 | 
			
		||||
    {%- endfor %}
 | 
			
		||||
 | 
			
		||||
    payload := map[string]interface{}{}
 | 
			
		||||
    payload["jsonrpc"] = "2.0"
 | 
			
		||||
    payload["id"] = 0
 | 
			
		||||
    payload["method"] = "{{ method_name }}"
 | 
			
		||||
    payload["params"] = params
 | 
			
		||||
 | 
			
		||||
    payloadBytes, err := json.Marshal(payload)
 | 
			
		||||
    if err != nil{
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'err')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resp, err := http.Post("{{url}}", "application/json", bytes.NewBuffer(payloadBytes))
 | 
			
		||||
    if err != nil{
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to make post request: %w", err)')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if resp.StatusCode >= 400{
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("request failed with status %d: %s", resp.StatusCode, resp.Status)')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {%- if return_type != 'nil' %}
 | 
			
		||||
    defer resp.Body.Close()
 | 
			
		||||
    body, err := io.ReadAll(resp.Body)
 | 
			
		||||
    if err != nil{
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to read response body: %w", err)')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mp := map[string]interface{}{}
 | 
			
		||||
    if err := json.Unmarshal(body, &mp); err != nil{
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to decode response body: %w", err)')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result, ok := mp["result"]
 | 
			
		||||
    if !ok {
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("invalid jsonrpc result: %v", mp)')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if result == nil {
 | 
			
		||||
        {%- if return_type == 'nil '%}
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'nil')}}
 | 
			
		||||
        {%- else %}
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("invalid jsonrpc result: {{return_type}} was expected but found nil")')}}
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {%- if generator.is_primitive(return_type) %}
 | 
			
		||||
    return result.({{return_type}}), nil
 | 
			
		||||
    {%- elif generator.is_array(return_type) %}
 | 
			
		||||
    resSlice := {{return_type}}{}
 | 
			
		||||
    for item := range result.([]intreface{}) {
 | 
			
		||||
        {%- if generator.is_primitive(return_type[2:]) %}
 | 
			
		||||
        resSlice = append(resSlice, item.({{return_type[2:]}}))
 | 
			
		||||
        {%- else %}
 | 
			
		||||
        tmp := {{return_type[2:]}}{}
 | 
			
		||||
        if err := mapstructure.Decode(item, &tmp); err != nil{
 | 
			
		||||
            return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to decode result: %w", err)')}}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resSlice = append(resSlice, tmp)
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
    }
 | 
			
		||||
    return resSlice, nil
 | 
			
		||||
    {%- else %}
 | 
			
		||||
    ret := {{return_type}}{}
 | 
			
		||||
    if err := mapstructure.Decode(result, &ret); err != nil{
 | 
			
		||||
        return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to decode result: %w", err)')}}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ret, nil
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
    {%- else %}
 | 
			
		||||
    return nil
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
}   
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
package {{package_name}}
 | 
			
		||||
{% for item in imports %}
 | 
			
		||||
import "{{item}}"
 | 
			
		||||
{%- endfor %}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
type {{type_name}} struct{
 | 
			
		||||
    {%- for property_name, property_info in properties.items() %}
 | 
			
		||||
    {%- if property_info.description %}
 | 
			
		||||
    // {{ property_info.description }}
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
    {{ generator.get_camel_case_name(property_name) }} {{ property_info.type_name }} `json:"{{property_name}}"`
 | 
			
		||||
    {%- endfor%}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								_archive/openrpc/generator/code/lang_code_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								_archive/openrpc/generator/code/lang_code_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
from abc import ABC, abstractmethod
 | 
			
		||||
from typing import Any, Dict, List, Optional, Union
 | 
			
		||||
from urllib.parse import ParseResult
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.model.common import (
 | 
			
		||||
    ReferenceObject,
 | 
			
		||||
    SchemaObject,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.methods import MethodObject
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyInfo:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        name: str,
 | 
			
		||||
        type_name: str,
 | 
			
		||||
        description: Optional[str] = None,
 | 
			
		||||
        example: Optional[str] = None,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.type_name = type_name
 | 
			
		||||
        self.description = description
 | 
			
		||||
        self.example = example
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LangCodeGenerator(ABC):
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def generate_imports(self) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def generate_object(
 | 
			
		||||
        self,
 | 
			
		||||
        type_name: str,
 | 
			
		||||
        properties: Dict[str, PropertyInfo],
 | 
			
		||||
    ):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def generate_method(
 | 
			
		||||
        self,
 | 
			
		||||
        method_spec: MethodObject,
 | 
			
		||||
        url: ParseResult,
 | 
			
		||||
        params: Dict[str, str],
 | 
			
		||||
        return_type: str,
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def string_primitive(self) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def integer_primitive(self) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def number_primitive(self) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def null_primitive(self) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def bool_primitive(self) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def is_primitive(self, type_name: str) -> bool:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def generate_multitype(self, path: List[str], types: List[Union[SchemaObject, ReferenceObject]]) -> str:
 | 
			
		||||
        """handles `anyOf` and `oneOf` in a json schema"""
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def array_of_type(self, type_name: str) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def encapsulate_types(self, path: List[str], types: List[Union[SchemaObject, ReferenceObject]]) -> str:
 | 
			
		||||
        """handles `allOf` in a json schema"""
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def generate_enum(self, enum: List[Any], type_name: str) -> str:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def type_to_method_result(self, type_name: str) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        convert type to method result
 | 
			
		||||
        - type_name can be empty
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
							
								
								
									
										205
									
								
								_archive/openrpc/generator/code/python/python_code_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								_archive/openrpc/generator/code/python/python_code_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,205 @@
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
from typing import Any, Dict, List
 | 
			
		||||
from urllib.parse import ParseResult
 | 
			
		||||
 | 
			
		||||
import inflect
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.code.lang_code_generator import LangCodeGenerator, PropertyInfo
 | 
			
		||||
from heroserver.openrpc.model.common import (
 | 
			
		||||
    ReferenceObject,
 | 
			
		||||
    SchemaObject,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.methods import MethodObject
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import (
 | 
			
		||||
    OpenRPCSpec,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
inflector = inflect.engine()
 | 
			
		||||
 | 
			
		||||
STRING_PRIMITIVE = "str"
 | 
			
		||||
INT_PRIMITIVE = "int"
 | 
			
		||||
FLOAT_PRIMITIVE = "float"
 | 
			
		||||
BOOL_PRMITIVE = "bool"
 | 
			
		||||
NONE_PRIMITIVE = "None"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PythonCodeGenerator(LangCodeGenerator):
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
        self.class_template = env.get_template("templates/class.jinja")
 | 
			
		||||
        self.enum_template = env.get_template("templates/enum.jinja")
 | 
			
		||||
        self.method_template = env.get_template("templates/method.jinja")
 | 
			
		||||
        self.pre_template = env.get_template("templates/pre.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate_imports(self) -> str:
 | 
			
		||||
        return self.pre_template.render()
 | 
			
		||||
 | 
			
		||||
    def generate_object(
 | 
			
		||||
        self,
 | 
			
		||||
        type_name: str,
 | 
			
		||||
        properties: Dict[str, PropertyInfo],
 | 
			
		||||
    ):
 | 
			
		||||
        # for name, info in properties.items():
 | 
			
		||||
        #     info["load_code"] = self.generate_load_code(name, info['type'], 'data', f'data["{name}"]')
 | 
			
		||||
 | 
			
		||||
        return self.class_template.render(python_code_generator=self, class_name=type_name, properties=properties)
 | 
			
		||||
 | 
			
		||||
    def generate_load_code(self, name: str, type_name: str, data_source: str, load_param: str) -> str:
 | 
			
		||||
        if type_name.startswith("Optional"):
 | 
			
		||||
            type_name = type_name.removeprefix("Optional[").removesuffix("]")
 | 
			
		||||
            return f'({self.generate_load_code(name, type_name, data_source)} if "{name}" in {data_source} else None)'
 | 
			
		||||
 | 
			
		||||
        if type_name.startswith("List"):
 | 
			
		||||
            type_name = type_name.removeprefix("List[").removesuffix("]")
 | 
			
		||||
            if self.is_primitive(type_name):
 | 
			
		||||
                return f'{data_source}.get("{name}")'
 | 
			
		||||
            return f'[{self.generate_load_code(name, type_name, data_source, 'item')} for item in {data_source}.get("{name}", [])]'
 | 
			
		||||
 | 
			
		||||
        if self.is_primitive(type_name):
 | 
			
		||||
            return f'{data_source}.get("{name}")'
 | 
			
		||||
 | 
			
		||||
        return f"{type_name}.load({load_param})"
 | 
			
		||||
 | 
			
		||||
    def generate_method(
 | 
			
		||||
        self,
 | 
			
		||||
        method_spec: MethodObject,
 | 
			
		||||
        url: ParseResult,
 | 
			
		||||
        params: Dict[str, str],
 | 
			
		||||
        return_type: str,
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        function_name = method_spec.name.lower().replace(".", "_")
 | 
			
		||||
        method_name = method_spec.name
 | 
			
		||||
        method_result = self.type_to_method_result(return_type)
 | 
			
		||||
        method_description = ""
 | 
			
		||||
        if method_spec.description:
 | 
			
		||||
            method_description = method_spec.description.replace("'", " ")
 | 
			
		||||
        method_description = method_description.replace("\n", "\n# ")
 | 
			
		||||
 | 
			
		||||
        method_example = ""
 | 
			
		||||
        if method_spec.examples and len(method_spec.examples) > 0:
 | 
			
		||||
            method_example = json.dumps(method_spec.examples[0], indent=4)
 | 
			
		||||
        method_example.replace("\n", "\n#")
 | 
			
		||||
 | 
			
		||||
        method_code = self.method_template.render(
 | 
			
		||||
            python_code_generator=self,
 | 
			
		||||
            base_url=f"{url.scheme}://{url.netloc}",
 | 
			
		||||
            url_path=url.path,
 | 
			
		||||
            function_name=function_name,
 | 
			
		||||
            method_name=method_name,
 | 
			
		||||
            method_params=params,
 | 
			
		||||
            method_result=method_result,
 | 
			
		||||
            return_type=return_type,
 | 
			
		||||
            method_description=method_description,
 | 
			
		||||
            method_example=method_example,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return method_code
 | 
			
		||||
 | 
			
		||||
    def string_primitive(self) -> str:
 | 
			
		||||
        return STRING_PRIMITIVE
 | 
			
		||||
 | 
			
		||||
    def integer_primitive(self) -> str:
 | 
			
		||||
        return INT_PRIMITIVE
 | 
			
		||||
 | 
			
		||||
    def number_primitive(self) -> str:
 | 
			
		||||
        return FLOAT_PRIMITIVE
 | 
			
		||||
 | 
			
		||||
    def null_primitive(self) -> str:
 | 
			
		||||
        return NONE_PRIMITIVE
 | 
			
		||||
 | 
			
		||||
    def bool_primitive(self) -> str:
 | 
			
		||||
        return BOOL_PRMITIVE
 | 
			
		||||
 | 
			
		||||
    def array_of_type(self, type_name: str) -> str:
 | 
			
		||||
        return f"List[{type_name}]"
 | 
			
		||||
 | 
			
		||||
    def generate_multitype(self, types: List[str]) -> str:
 | 
			
		||||
        if len(types) > 2:
 | 
			
		||||
            raise Exception("only a type and null are supported with anyOf/allOf keyword")
 | 
			
		||||
 | 
			
		||||
        if len(types) == 1:
 | 
			
		||||
            return types[0]
 | 
			
		||||
 | 
			
		||||
        if types[0] == NONE_PRIMITIVE:
 | 
			
		||||
            return f"Optional[{types[1]}]"
 | 
			
		||||
        if types[1] == NONE_PRIMITIVE:
 | 
			
		||||
            return f"Optional[{types[0]}]"
 | 
			
		||||
 | 
			
		||||
        raise Exception("only a type and null are supported with anyOf/allOf keyword")
 | 
			
		||||
 | 
			
		||||
    def encapsulate_types(self, path: List[str], types: List[SchemaObject | ReferenceObject]) -> str:
 | 
			
		||||
        raise Exception("no support for allOf keyword")
 | 
			
		||||
 | 
			
		||||
    def generate_enum(self, enum: List[Any], type_name: str) -> str:
 | 
			
		||||
        if all(isinstance(elem, str) for elem in enum):
 | 
			
		||||
            # enum of strings
 | 
			
		||||
            return self.enum_template.render(
 | 
			
		||||
                enum=enum,
 | 
			
		||||
                type_name=type_name,
 | 
			
		||||
                number_to_words=inflector.number_to_words,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        elif all(isinstance(elem, int) for elem in enum):
 | 
			
		||||
            # enum of integers
 | 
			
		||||
            return self.enum_template.render(
 | 
			
		||||
                is_integer=True,
 | 
			
		||||
                enum=enum,
 | 
			
		||||
                type_name=type_name,
 | 
			
		||||
                number_to_words=inflector.number_to_words,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(f"failed to generate enum code for: {enum}")
 | 
			
		||||
 | 
			
		||||
    def type_to_method_result(self, type_name: str) -> str:
 | 
			
		||||
        return type_name
 | 
			
		||||
 | 
			
		||||
    def get_method_params(self, method_params: Dict[str, str]) -> str:
 | 
			
		||||
        return ", ".join([f"{param_name}: {param_type}" for param_name, param_type in method_params.items()])
 | 
			
		||||
 | 
			
		||||
    def is_primitive(self, type_name: str) -> bool:
 | 
			
		||||
        return type_name in [STRING_PRIMITIVE, INT_PRIMITIVE, FLOAT_PRIMITIVE, BOOL_PRMITIVE] or any(
 | 
			
		||||
            type_name.startswith(end) for end in ["List", "Optional", "Union"]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_pydantic_field_params(self, prop_info: PropertyInfo) -> str:
 | 
			
		||||
        field_str = ""
 | 
			
		||||
        if prop_info.type_name.startswith("Optional"):
 | 
			
		||||
            field_str = "None"
 | 
			
		||||
        else:
 | 
			
		||||
            field_str = "..."
 | 
			
		||||
 | 
			
		||||
        if prop_info.description:
 | 
			
		||||
            field_str += f', description="{prop_info.description}"'
 | 
			
		||||
 | 
			
		||||
        if prop_info.example:
 | 
			
		||||
            if isinstance(prop_info.example, str):
 | 
			
		||||
                example_formatted = f'"{prop_info.example}"'
 | 
			
		||||
            else:
 | 
			
		||||
                example_formatted = prop_info.example
 | 
			
		||||
            field_str += f", examples=[{example_formatted}]"
 | 
			
		||||
 | 
			
		||||
        return f"Field({field_str})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# main()
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    import yaml
 | 
			
		||||
 | 
			
		||||
    from heroserver.openrpc.generator.generator import ClientGenerator
 | 
			
		||||
 | 
			
		||||
    with open("/root/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/mycelium_openrpc.yaml", "r") as file:
 | 
			
		||||
        data = yaml.safe_load(file)
 | 
			
		||||
        # print(data)
 | 
			
		||||
        spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
        python_code_generator = PythonCodeGenerator()
 | 
			
		||||
        generator = ClientGenerator(
 | 
			
		||||
            spec_object,
 | 
			
		||||
            python_code_generator,
 | 
			
		||||
            "/tmp/python_client.py",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        generator.generate_client()
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
class {{ class_name }}(BaseModel):
 | 
			
		||||
    {% for prop_name, prop_info in properties.items() -%}
 | 
			
		||||
    {{ prop_name }}: {{prop_info.type_name}} = {{python_code_generator.get_pydantic_field_params(prop_info)}}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
							
								
								
									
										18
									
								
								_archive/openrpc/generator/code/python/templates/enum.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								_archive/openrpc/generator/code/python/templates/enum.jinja
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
{% if is_integer %}
 | 
			
		||||
class {{ type_name }}(Enum):
 | 
			
		||||
    {% for elem in enum -%}
 | 
			
		||||
    {{ number_to_words(elem) }} = {{ elem }}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
{% else -%}
 | 
			
		||||
class {{ type_name }}(str, Enum):
 | 
			
		||||
    {% for elem in enum -%}
 | 
			
		||||
    {{ elem.upper() }} = '{{ elem }}'
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
    {# @classmethod
 | 
			
		||||
    def load(cls, data: Dict[str, Any]) -> "{{type_name}}":
 | 
			
		||||
        return cls(
 | 
			
		||||
            {% for elem in enum -%}
 | 
			
		||||
            {{elem}} = data.get('{{elem}}'),
 | 
			
		||||
            {% endfor %}   
 | 
			
		||||
        ) #}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
{% if method_example != "" -%}
 | 
			
		||||
# Example:
 | 
			
		||||
# {{ method_example }}
 | 
			
		||||
{% endif -%}
 | 
			
		||||
def {{ function_name }}({{ python_code_generator.get_method_params(method_params) }}){% if method_result %} -> {{ method_result }}{% endif %}:
 | 
			
		||||
    {% if method_description != "" -%}
 | 
			
		||||
    """
 | 
			
		||||
    {{ method_description }}
 | 
			
		||||
    """
 | 
			
		||||
    {% endif -%}
 | 
			
		||||
    url = "{{base_url}}"
 | 
			
		||||
    headers = {"content-type": "application/json"}
 | 
			
		||||
 | 
			
		||||
    params = {
 | 
			
		||||
        {% for param_name, param_type in method_params.items() -%}
 | 
			
		||||
        '{{ param_name }}': {{ param_name }},
 | 
			
		||||
        {% endfor -%}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    response = requests.post(url, json={"jsonrpc": "2.0", "id": 0, 'method': '{{ method_name  }}', 'params': params}, headers=headers).json()
 | 
			
		||||
 | 
			
		||||
    {% if return_type -%}
 | 
			
		||||
    {% if python_code_generator.is_primitive(return_type) -%}
 | 
			
		||||
    return response['result']
 | 
			
		||||
    {% else -%}
 | 
			
		||||
    return {{return_type}}(response['result'])
 | 
			
		||||
    {% endif -%}
 | 
			
		||||
    {% else -%}
 | 
			
		||||
    response.raise_for_status()
 | 
			
		||||
    {% endif -%}
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
from typing import List, Optional, Union, Any, Dict
 | 
			
		||||
from pydantic import BaseModel, Field
 | 
			
		||||
from enum import Enum
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										205
									
								
								_archive/openrpc/generator/code/vlang/handler_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								_archive/openrpc/generator/code/vlang/handler_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,205 @@
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Dict, Union
 | 
			
		||||
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.model_generator import ModelGenerator
 | 
			
		||||
from heroserver.openrpc.generator.vlang.vlang_code_generator import VlangGenerator
 | 
			
		||||
from heroserver.openrpc.model.common import ContentDescriptorObject, ReferenceObject
 | 
			
		||||
from heroserver.openrpc.model.methods import MethodObject
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_actor_executor_name(actor: str) -> str:
 | 
			
		||||
    return f"{''.join([part.title() for part in actor.split('_')])}Executor"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActorGenerator:
 | 
			
		||||
    def __init__(self, actor: str, spec: OpenRPCSpec, dir: Path) -> None:
 | 
			
		||||
        self.spec = spec
 | 
			
		||||
        self.actor = actor
 | 
			
		||||
        self.dir = dir
 | 
			
		||||
        self.model_generator = ModelGenerator(spec, VlangGenerator())
 | 
			
		||||
        self.executor_template = env.get_template("templates/executor.jinja")
 | 
			
		||||
        self.pre_template = env.get_template("templates/pre.jinja")
 | 
			
		||||
        self.internal_crud_methods_template = env.get_template("templates/internal_crud_methods.jinja")
 | 
			
		||||
        self.internal_actor_method_template = env.get_template("templates/internal_actor_method.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
        self.generate_models()
 | 
			
		||||
        self.generate_crud()
 | 
			
		||||
        self.generate_internal_actor_methods()
 | 
			
		||||
        self.generate_executor()
 | 
			
		||||
 | 
			
		||||
    def generate_models(self):
 | 
			
		||||
        pre = self.pre_template.render(module_name="myhandler", imports=[])
 | 
			
		||||
        code = self.model_generator.generate_models()
 | 
			
		||||
        path = self.dir.joinpath(f"{self.actor}_models.v")
 | 
			
		||||
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{pre}\n\n{code}\n")
 | 
			
		||||
 | 
			
		||||
    def generate_crud(self):
 | 
			
		||||
        imports = self.pre_template.render(
 | 
			
		||||
            module_name="myhandler",
 | 
			
		||||
            imports=["json", "freeflowuniverse.crystallib.baobab.backend"],
 | 
			
		||||
        )
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
            methods += (
 | 
			
		||||
                self.internal_crud_methods_template.render(
 | 
			
		||||
                    variable_name=variable_name,
 | 
			
		||||
                    type_name=type_name,
 | 
			
		||||
                    actor_executor_name=get_actor_executor_name(self.actor),
 | 
			
		||||
                )
 | 
			
		||||
                + "\n\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath(f"{self.actor}_crud.v")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_internal_actor_methods(self):
 | 
			
		||||
        pre = self.pre_template.render(module_name="myhandler", imports=[])
 | 
			
		||||
        for method in self.spec.methods:
 | 
			
		||||
            function_name = method.name.lower().replace(".", "_") + "_internal"
 | 
			
		||||
            file_path = self.dir.joinpath(f"{self.actor}_{function_name}.v")
 | 
			
		||||
            if file_path.exists():
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
 | 
			
		||||
 | 
			
		||||
            return_type = self.get_method_return_type(method)
 | 
			
		||||
            method_params = ", ".join([f"{param.name} {self.get_param_type(method.name, param)}" for param in method.params])
 | 
			
		||||
 | 
			
		||||
            code = self.internal_actor_method_template.render(
 | 
			
		||||
                function_name=function_name,
 | 
			
		||||
                method_params=method_params,
 | 
			
		||||
                return_type=return_type,
 | 
			
		||||
                actor_executor_name=get_actor_executor_name(self.actor),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            with open(file_path, "w") as file:
 | 
			
		||||
                file.write(f"{pre}\n\n{code}")
 | 
			
		||||
 | 
			
		||||
    def generate_executor(self):
 | 
			
		||||
        pre = self.pre_template.render(
 | 
			
		||||
            module_name="myhandler",
 | 
			
		||||
            imports=[
 | 
			
		||||
                "x.json2",
 | 
			
		||||
                "json",
 | 
			
		||||
                "freeflowuniverse.crystallib.clients.redisclient",
 | 
			
		||||
                "freeflowuniverse.crystallib.baobab.backend",
 | 
			
		||||
                "freeflowuniverse.crystallib.rpc.jsonrpc",
 | 
			
		||||
            ],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        code = self.executor_template.render(
 | 
			
		||||
            generator=self,
 | 
			
		||||
            actor_executor_name=get_actor_executor_name(self.actor),
 | 
			
		||||
            methods=self.spec.methods,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath(f"{self.actor}_executor.v")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{pre}\n\n{code}")
 | 
			
		||||
 | 
			
		||||
    def get_param_type(
 | 
			
		||||
        self,
 | 
			
		||||
        method_name: str,
 | 
			
		||||
        param: Union[ContentDescriptorObject, ReferenceObject],
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        type_name = self.model_generator.jsonschema_to_type(["methods", method_name, "params", param.name], param.schema)
 | 
			
		||||
        return type_name
 | 
			
		||||
 | 
			
		||||
    def get_method_return_type(self, method: MethodObject) -> str:
 | 
			
		||||
        if not method.result:
 | 
			
		||||
            return ""
 | 
			
		||||
 | 
			
		||||
        path = ["methods", method.name, "result"]
 | 
			
		||||
        schema = method.result
 | 
			
		||||
        if isinstance(method.result, ContentDescriptorObject):
 | 
			
		||||
            schema = method.result.schema
 | 
			
		||||
 | 
			
		||||
        return self.model_generator.jsonschema_to_type(path, schema)
 | 
			
		||||
 | 
			
		||||
    def is_primitive(self, type_name: str) -> bool:
 | 
			
		||||
        return self.model_generator.lang_code_generator.is_primitive(type_name)
 | 
			
		||||
 | 
			
		||||
    def get_method_params_as_args(self, method: MethodObject) -> str:
 | 
			
		||||
        return ", ".join([param.name for param in method.params])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Generator:
 | 
			
		||||
    def generate_handler(self, specs_dir: Path, output_dir: Path):
 | 
			
		||||
        output_dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        handler_template = env.get_template("templates/handler.jinja")
 | 
			
		||||
        handler_test_template = env.get_template("templates/handler_test.jinja")
 | 
			
		||||
        pre_template = env.get_template("templates/pre.jinja")
 | 
			
		||||
        actors = []
 | 
			
		||||
        method_names = []
 | 
			
		||||
 | 
			
		||||
        pre = pre_template.render(
 | 
			
		||||
            module_name="myhandler",
 | 
			
		||||
            imports=[
 | 
			
		||||
                "freeflowuniverse.crystallib.clients.redisclient",
 | 
			
		||||
                "freeflowuniverse.crystallib.baobab.backend",
 | 
			
		||||
                "freeflowuniverse.crystallib.rpc.jsonrpc",
 | 
			
		||||
            ],
 | 
			
		||||
        )
 | 
			
		||||
        code = ""
 | 
			
		||||
        for item in specs_dir.iterdir():
 | 
			
		||||
            if not item.is_dir():
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            actors.append(item.name)
 | 
			
		||||
 | 
			
		||||
            data = parser(path=item.as_posix())
 | 
			
		||||
            openrpc_spec = OpenRPCSpec.load(data)
 | 
			
		||||
            actor_generator = ActorGenerator(item.name, openrpc_spec, output_dir)
 | 
			
		||||
            actor_generator.generate()
 | 
			
		||||
 | 
			
		||||
            for method in openrpc_spec.methods:
 | 
			
		||||
                method_names.append(f"{item.name}.{method.name}")
 | 
			
		||||
 | 
			
		||||
        code = handler_template.render(actors=actors, get_actor_executor_name=get_actor_executor_name)
 | 
			
		||||
 | 
			
		||||
        handler_path = output_dir.joinpath("handler.v")
 | 
			
		||||
        with open(handler_path, "w") as file:
 | 
			
		||||
            file.write(f"{pre}\n\n{code}")
 | 
			
		||||
 | 
			
		||||
        handler_test_path = output_dir.joinpath("handler_test.v")
 | 
			
		||||
        with open(handler_test_path, "w") as file:
 | 
			
		||||
            file.write(handler_test_template.render(method_names=method_names))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
    generator = Generator()
 | 
			
		||||
    path = "~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs"
 | 
			
		||||
    generator.generate_handler(Path(path), Path("/tmp/myhandler"))
 | 
			
		||||
    # vlang_code_generator = VlangGenerator()
 | 
			
		||||
    # generator = ClientGenerator(
 | 
			
		||||
    #     spec_object,
 | 
			
		||||
    #     vlang_code_generator,
 | 
			
		||||
    #     "/tmp/v_client_new.v",
 | 
			
		||||
    # )
 | 
			
		||||
 | 
			
		||||
    # generator.generate_client()
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
pub enum {{ type_name }}{
 | 
			
		||||
    {% for elem in enum -%}
 | 
			
		||||
    {% if is_integer -%}
 | 
			
		||||
    {{ number_to_words(elem) }} = {{ elem }}
 | 
			
		||||
    {% else -%}
 | 
			
		||||
    {{ elem }}
 | 
			
		||||
    {% endif -%}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,77 @@
 | 
			
		||||
pub struct {{ actor_executor_name }}{
 | 
			
		||||
pub mut:
 | 
			
		||||
    db &backend.Backend
 | 
			
		||||
    redis &redisclient.Redis
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut executor {{ actor_executor_name }}) execute(rpc_msg_id string, rpc_msg_method string, rpc_msg_params_str string) {
 | 
			
		||||
    raw_params := json2.raw_decode(rpc_msg_params_str) or{
 | 
			
		||||
        executor.return_error(rpc_msg_id, jsonrpc.invalid_params)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    params_arr := raw_params.arr()
 | 
			
		||||
 | 
			
		||||
    match rpc_msg_method {
 | 
			
		||||
        {%- for method in methods %}
 | 
			
		||||
        '{{method.name}}' {
 | 
			
		||||
            {%- for param in method.params %}
 | 
			
		||||
            {%- if generator.is_primitive(generator.get_param_type(method.name, param))%}
 | 
			
		||||
            {{param.name}} := params_arr[{{loop.index0}}] as {{generator.get_param_type(method.name, param)}}
 | 
			
		||||
            {%- else %}
 | 
			
		||||
            {{param.name}} := json.decode({{generator.get_param_type(method.name, param)}}, params_arr[{{loop.index0}}].json_str()) or {
 | 
			
		||||
                executor.return_error(rpc_msg_id, jsonrpc.invalid_request)
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            {%- endif %}
 | 
			
		||||
            {%- endfor %}
 | 
			
		||||
 | 
			
		||||
            {%- if generator.get_method_return_type(method) == 'none' %}
 | 
			
		||||
            executor.{{method.name}}_internal({{generator.get_method_params_as_args(method)}}) or {
 | 
			
		||||
                executor.return_error(rpc_msg_id, jsonrpc.InnerJsonRpcError{
 | 
			
		||||
                    code: 32000
 | 
			
		||||
                    message: '${err}'
 | 
			
		||||
                })
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            response := jsonrpc.JsonRpcResponse[string]{
 | 
			
		||||
                jsonrpc: '2.0.0'
 | 
			
		||||
                id: rpc_msg_id
 | 
			
		||||
                result: ''
 | 
			
		||||
            }
 | 
			
		||||
            {%- else %}
 | 
			
		||||
            result := executor.{{method.name}}_internal({{generator.get_method_params_as_args(method)}}) or {
 | 
			
		||||
                executor.return_error(rpc_msg_id, jsonrpc.InnerJsonRpcError{
 | 
			
		||||
                    code: 32000
 | 
			
		||||
                    message: '${err}'
 | 
			
		||||
                })
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            response := jsonrpc.JsonRpcResponse[{{generator.get_method_return_type(method)}}]{
 | 
			
		||||
                jsonrpc: '2.0.0'
 | 
			
		||||
                id: rpc_msg_id
 | 
			
		||||
                result: result
 | 
			
		||||
            }
 | 
			
		||||
            {%- endif %}
 | 
			
		||||
 | 
			
		||||
            // put response in response queue
 | 
			
		||||
            executor.redis.lpush(rpc_msg_id, response.to_json()) or {
 | 
			
		||||
                println('failed to push response for ${rpc_msg_id} to redis queue: ${err}')
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {%- endfor %}
 | 
			
		||||
        else {
 | 
			
		||||
            executor.return_error(rpc_msg_id, jsonrpc.method_not_found)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut executor {{actor_executor_name}}) return_error(rpc_msg_id string, error jsonrpc.InnerJsonRpcError){
 | 
			
		||||
    response := jsonrpc.new_jsonrpcerror(rpc_msg_id, error)
 | 
			
		||||
    executor.redis.lpush(rpc_msg_id, response.to_json()) or {
 | 
			
		||||
        println('failed to push response for ${rpc_msg_id} to redis queue: ${err}')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
struct Handler {
 | 
			
		||||
pub mut:
 | 
			
		||||
    db &backend.Backend
 | 
			
		||||
    redis &redisclient.Redis
 | 
			
		||||
    {% for actor in actors %}
 | 
			
		||||
    {{actor}}_executor {{get_actor_executor_name(actor)}}
 | 
			
		||||
    {%- endfor %}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn new(db_config backend.BackendConfig, redis_addr string) !Handler{
 | 
			
		||||
    db := backend.new(db_config)!
 | 
			
		||||
    mut redis_client := redisclient.new([redis_addr])!
 | 
			
		||||
    redis_client.selectdb(0)!
 | 
			
		||||
    
 | 
			
		||||
    return Handler{
 | 
			
		||||
        db: &db
 | 
			
		||||
        redis: &redis_client
 | 
			
		||||
        {%- for actor in actors %}
 | 
			
		||||
        {{actor}}_executor: {{get_actor_executor_name(actor)}}{
 | 
			
		||||
            db: &db
 | 
			
		||||
            redis: &redis_client
 | 
			
		||||
        }
 | 
			
		||||
        {%- endfor %}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handle handles an incoming JSON-RPC encoded message and returns an encoded response
 | 
			
		||||
pub fn (mut handler Handler) handle(id string, method string, params_str string) {
 | 
			
		||||
    actor := method.all_before('.')
 | 
			
		||||
    method_name := method.all_after('.')
 | 
			
		||||
 | 
			
		||||
    match actor {
 | 
			
		||||
        {%- for actor in actors %}
 | 
			
		||||
        '{{ actor }}' {
 | 
			
		||||
            spawn (&handler.{{actor}}_executor).execute(id, method_name, params_str)
 | 
			
		||||
        }
 | 
			
		||||
        {%- endfor %}
 | 
			
		||||
        else {
 | 
			
		||||
            handler.return_error(id, jsonrpc.method_not_found)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut handler Handler) return_error(rpc_msg_id string, error jsonrpc.InnerJsonRpcError){
 | 
			
		||||
    response := jsonrpc.new_jsonrpcerror(rpc_msg_id, error)
 | 
			
		||||
    handler.redis.lpush(rpc_msg_id, response.to_json()) or {
 | 
			
		||||
        println('failed to push response for ${rpc_msg_id} to redis queue: ${err}')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
module myhandler
 | 
			
		||||
 | 
			
		||||
import x.json2
 | 
			
		||||
import rand
 | 
			
		||||
import freeflowuniverse.crystallib.baobab.backend
 | 
			
		||||
 | 
			
		||||
fn test_handler(){
 | 
			
		||||
	db_config := backend.BackendConfig{
 | 
			
		||||
		name:   'myhandler'
 | 
			
		||||
		secret: 'secret'
 | 
			
		||||
		reset: true
 | 
			
		||||
		db_type: .postgres
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mut handler := new(db_config, '127.0.0.1:6379')!
 | 
			
		||||
	{% for method_name in method_names %}
 | 
			
		||||
	do_request(mut handler, '{{method_name}}')!
 | 
			
		||||
	{%- endfor %}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn do_request(mut handler Handler, method_name string) ! {
 | 
			
		||||
	// TODO: edit input parameters
 | 
			
		||||
	mut params := []json2.Any{}
 | 
			
		||||
	params << "objid"
 | 
			
		||||
	params << "blabla_name"
 | 
			
		||||
	params_str := json2.Any(params).json_str()
 | 
			
		||||
	
 | 
			
		||||
	id := rand.string(6)
 | 
			
		||||
	handler.handle(rand.string(6), method_name, json2.Any(params).json_str())
 | 
			
		||||
	println('request id: ${id}')
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
pub fn (mut executor {{ actor_executor_name }}) {{function_name}}({{method_params}}) !{{return_type}}{
 | 
			
		||||
    // context allows us to see who the user is and which groups the user is
 | 
			
		||||
    // context also gives a logging feature
 | 
			
		||||
    // context is linked to 1 circle
 | 
			
		||||
    // context is linked to a DB (OSIS)
 | 
			
		||||
    panic('implement')
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
pub fn (mut executor {{ actor_executor_name }}) {{variable_name}}_get_internal(id string) !{{type_name}}{
 | 
			
		||||
    json_str := executor.db.indexer.get_json(id, backend.RootObject{
 | 
			
		||||
        name: '{{type_name}}'
 | 
			
		||||
    })!
 | 
			
		||||
    
 | 
			
		||||
    return json.decode({{type_name}}, json_str)!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut executor {{ actor_executor_name }}) {{variable_name}}_set_internal({{variable_name}} {{type_name}}) !{
 | 
			
		||||
    if {{variable_name}}.oid != ''{
 | 
			
		||||
        executor.db.indexer.set(backend.RootObject{
 | 
			
		||||
            id: {{variable_name}}.oid
 | 
			
		||||
            name: '{{type_name}}'
 | 
			
		||||
        })!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    executor.db.indexer.new(backend.RootObject{
 | 
			
		||||
        name: '{{type_name}}'
 | 
			
		||||
    })!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn (mut executor {{ actor_executor_name }}) {{variable_name}}_delete_internal(id string) !{
 | 
			
		||||
    executor.db.indexer.delete(id, backend.RootObject{
 | 
			
		||||
        name: '{{type_name}}'
 | 
			
		||||
    })!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
pub struct {{method_param_struct_name}}{
 | 
			
		||||
    {% for param_name, param_type in params.items()%}
 | 
			
		||||
    {{param_name}} {{param_type}}
 | 
			
		||||
    {%- endfor %}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
{% if method_example -%}
 | 
			
		||||
/*
 | 
			
		||||
Example:
 | 
			
		||||
{{ method_example }}
 | 
			
		||||
*/
 | 
			
		||||
{% endif -%}
 | 
			
		||||
 | 
			
		||||
{% if method_description -%}
 | 
			
		||||
/*
 | 
			
		||||
{{ method_description }}
 | 
			
		||||
*/
 | 
			
		||||
{% endif -%}
 | 
			
		||||
pub fn {{ function_name }}({{ vlang_code_generator.get_method_params(method_params) }}) {{ method_result }}{
 | 
			
		||||
    mut conn := httpconnection.new(
 | 
			
		||||
		name: 'openrpc_client'
 | 
			
		||||
		url: '{{ base_url }}'
 | 
			
		||||
	)!
 | 
			
		||||
 | 
			
		||||
    mut params := map[string]json2.Any{}
 | 
			
		||||
    {% for param_name, param_type in method_params.items() -%}
 | 
			
		||||
    {% if vlang_code_generator.is_primitive(param_type) %}
 | 
			
		||||
    params["{{ param_name }}"] = {{ param_name }}
 | 
			
		||||
    {% elif vlang_code_generator.is_vlang_array(param_type) %}
 | 
			
		||||
    mut any_arr := []json2.Any{}
 | 
			
		||||
    for item in {{ param_name }}{
 | 
			
		||||
        {% if vlang_code_generator.is_primitive(param_type[2:]) %}
 | 
			
		||||
        any_arr << item
 | 
			
		||||
        {% else %}
 | 
			
		||||
        any_arr << json2.raw_decode(json2.encode(item))!
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    }
 | 
			
		||||
    params["{{ param_name }}"] = json2.Any(any_arr)
 | 
			
		||||
    {%else %}
 | 
			
		||||
    params["{{ param_name }}"] = json2.raw_decode(json2.encode({{ param_name }}))!
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% endfor -%}
 | 
			
		||||
 | 
			
		||||
    mut payload := map[string]json2.Any{}
 | 
			
		||||
    payload['jsonrpc'] = "2.0"
 | 
			
		||||
    payload['id'] = 0
 | 
			
		||||
    payload['method'] = '{{ method_name }}'
 | 
			
		||||
    payload['params'] = params
 | 
			
		||||
 | 
			
		||||
    response := conn.send(method: .post, data: json2.encode(payload){% if url_path -%}, prefix: '{{ url_path }}' {% endif -%})!
 | 
			
		||||
	if !response.is_ok() {
 | 
			
		||||
		return error('failed to make rpc request: (${response.code}) ${response.data}')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    {% if return_type != 'none' %}
 | 
			
		||||
	mp := json2.raw_decode(response.data)!.as_map()
 | 
			
		||||
    res := mp['result'] or {
 | 
			
		||||
        return error('invalid jsonrpc result: ${response.data}')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if res is json2.Null{
 | 
			
		||||
        return error('not found')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {% if vlang_code_generator.is_primitive(return_type) %}
 | 
			
		||||
    return res as {{return_type}}
 | 
			
		||||
    {% elif vlang_code_generator.is_vlang_array(return_type) %}
 | 
			
		||||
    mut res_arr := {{return_type}}
 | 
			
		||||
    for item in res.arr() {
 | 
			
		||||
        {% if vlang_code_generator.is_primitive(return_type[2:]) %}
 | 
			
		||||
        res_arr << item as {{return_type}}
 | 
			
		||||
        {% else %}
 | 
			
		||||
        res_arr << json2.decode[{{return_type[2:]}}](item.json_str())!
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    }
 | 
			
		||||
    return res_arr
 | 
			
		||||
    {%else %}
 | 
			
		||||
    return json2.decode[{{return_type}}](res.json_str())!
 | 
			
		||||
    {% endif -%}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
}   
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
module {{module_name}}
 | 
			
		||||
{% for item in imports %}
 | 
			
		||||
import {{item}}
 | 
			
		||||
{%- endfor %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								_archive/openrpc/generator/code/vlang/templates/struct.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								_archive/openrpc/generator/code/vlang/templates/struct.jinja
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
@[params]
 | 
			
		||||
pub struct {{ type_name }}{
 | 
			
		||||
pub mut:
 | 
			
		||||
    {%- for property_name, property_info in properties.items() %}
 | 
			
		||||
    {%- if property_info.description %}
 | 
			
		||||
    // {{ property_info.description }}
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
    {{ property_name }} {{ property_info.type_name }}
 | 
			
		||||
    {%- endfor %}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								_archive/openrpc/generator/code/vlang/vlang_code_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								_archive/openrpc/generator/code/vlang/vlang_code_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
from typing import Any, Dict, List
 | 
			
		||||
from urllib.parse import ParseResult
 | 
			
		||||
 | 
			
		||||
import inflect
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.lang_code_generator import LangCodeGenerator, PropertyInfo
 | 
			
		||||
from heroserver.openrpc.model.common import (
 | 
			
		||||
    ReferenceObject,
 | 
			
		||||
    SchemaObject,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.methods import MethodObject
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import (
 | 
			
		||||
    OpenRPCSpec,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
inflector = inflect.engine()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VlangGenerator(LangCodeGenerator):
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
        self.struct_template = env.get_template("templates/struct.jinja")
 | 
			
		||||
        self.enum_template = env.get_template("templates/enum.jinja")
 | 
			
		||||
        self.methods_template = env.get_template("templates/methods.jinja")
 | 
			
		||||
        self.pre_template = env.get_template("templates/pre.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate_imports(self) -> str:
 | 
			
		||||
        return self.pre_template.render()
 | 
			
		||||
 | 
			
		||||
    def generate_object(
 | 
			
		||||
        self,
 | 
			
		||||
        type_name: str,
 | 
			
		||||
        properties: Dict[str, PropertyInfo],
 | 
			
		||||
    ):
 | 
			
		||||
        return self.struct_template.render(type_name=type_name, properties=properties)
 | 
			
		||||
 | 
			
		||||
    def generate_method(
 | 
			
		||||
        self,
 | 
			
		||||
        method_spec: MethodObject,
 | 
			
		||||
        url: ParseResult,
 | 
			
		||||
        params: Dict[str, str],
 | 
			
		||||
        return_type: str,
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        function_name = method_spec.name.lower().replace(".", "_")
 | 
			
		||||
        method_name = method_spec.name
 | 
			
		||||
        method_result = self.type_to_method_result(return_type)
 | 
			
		||||
        method_description = ""
 | 
			
		||||
        if method_spec.description:
 | 
			
		||||
            method_description = method_spec.description.replace("'", " ")
 | 
			
		||||
 | 
			
		||||
        method_example = ""
 | 
			
		||||
        if method_spec.examples and len(method_spec.examples) > 0:
 | 
			
		||||
            method_example = json.dumps(method_spec.examples[0], indent=4)
 | 
			
		||||
 | 
			
		||||
        method_code = self.methods_template.render(
 | 
			
		||||
            vlang_code_generator=self,
 | 
			
		||||
            base_url=f"{url.scheme}://{url.netloc}",
 | 
			
		||||
            url_path=url.path,
 | 
			
		||||
            function_name=function_name,
 | 
			
		||||
            method_name=method_name,
 | 
			
		||||
            method_params=params,
 | 
			
		||||
            method_result=method_result,
 | 
			
		||||
            return_type=return_type,
 | 
			
		||||
            method_description=method_description,
 | 
			
		||||
            method_example=method_example,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return method_code
 | 
			
		||||
 | 
			
		||||
    def string_primitive(self) -> str:
 | 
			
		||||
        return "string"
 | 
			
		||||
 | 
			
		||||
    def integer_primitive(self) -> str:
 | 
			
		||||
        return "i64"
 | 
			
		||||
 | 
			
		||||
    def number_primitive(self) -> str:
 | 
			
		||||
        return "f64"
 | 
			
		||||
 | 
			
		||||
    def null_primitive(self) -> str:
 | 
			
		||||
        return "none"
 | 
			
		||||
 | 
			
		||||
    def bool_primitive(self) -> str:
 | 
			
		||||
        return "bool"
 | 
			
		||||
 | 
			
		||||
    def array_of_type(self, type_name: str) -> str:
 | 
			
		||||
        return f"[]{type_name}"
 | 
			
		||||
 | 
			
		||||
    def generate_multitype(self, types: List[str]) -> str:
 | 
			
		||||
        if len(types) > 2:
 | 
			
		||||
            raise Exception("only a type and null are supported with anyOf/allOf keyword")
 | 
			
		||||
 | 
			
		||||
        if len(types) == 1:
 | 
			
		||||
            return types[0]
 | 
			
		||||
 | 
			
		||||
        if types[0] == "none":
 | 
			
		||||
            return f"?{types[1]}"
 | 
			
		||||
        if types[1] == "none":
 | 
			
		||||
            return f"?{types[0]}"
 | 
			
		||||
 | 
			
		||||
        raise Exception("only a type and null are supported with anyOf/allOf keyword")
 | 
			
		||||
 | 
			
		||||
    def encapsulate_types(self, path: List[str], types: List[SchemaObject | ReferenceObject]) -> str:
 | 
			
		||||
        raise Exception("no support for allOf keyword")
 | 
			
		||||
 | 
			
		||||
    def generate_enum(self, enum: List[Any], type_name: str) -> str:
 | 
			
		||||
        if all(isinstance(elem, str) for elem in enum):
 | 
			
		||||
            # enum of strings
 | 
			
		||||
            return self.enum_template.render(
 | 
			
		||||
                enum=enum,
 | 
			
		||||
                type_name=type_name,
 | 
			
		||||
                number_to_words=inflector.number_to_words,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        elif all(isinstance(elem, int) for elem in enum):
 | 
			
		||||
            # enum of integers
 | 
			
		||||
            return self.enum_template.render(
 | 
			
		||||
                is_integer=True,
 | 
			
		||||
                enum=enum,
 | 
			
		||||
                type_name=type_name,
 | 
			
		||||
                number_to_words=inflector.number_to_words,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception(f"failed to generate enum code for: {enum}")
 | 
			
		||||
 | 
			
		||||
    def type_to_method_result(self, type_name: str) -> str:
 | 
			
		||||
        if type_name == "none":
 | 
			
		||||
            type_name = ""
 | 
			
		||||
 | 
			
		||||
        if type_name.startswith("?"):
 | 
			
		||||
            type_name = type_name[1:]
 | 
			
		||||
 | 
			
		||||
        return "!" + type_name
 | 
			
		||||
 | 
			
		||||
    def is_primitive(self, type: str) -> bool:
 | 
			
		||||
        return type in ["u64", "f64", "i64", "int", "bool", "string"]
 | 
			
		||||
 | 
			
		||||
    def is_vlang_array(self, type: str) -> bool:
 | 
			
		||||
        return type.startswith("[]")
 | 
			
		||||
 | 
			
		||||
    def get_method_params(self, method_params: Dict[str, str]) -> str:
 | 
			
		||||
        return ", ".join([f"{param_name} {param_type}" for param_name, param_type in method_params.items()])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# main()
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    from heroserver.openrpc.generator.generator import ClientGenerator
 | 
			
		||||
    from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
    data = parser(path="~/code/git.threefold.info/projectmycelium/hero_server/lib/openrpclib/parser/examples")
 | 
			
		||||
 | 
			
		||||
    spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
    vlang_code_generator = VlangGenerator()
 | 
			
		||||
    generator = ClientGenerator(
 | 
			
		||||
        spec_object,
 | 
			
		||||
        vlang_code_generator,
 | 
			
		||||
        "/tmp/v_client_new.v",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    generator.generate_client()
 | 
			
		||||
							
								
								
									
										46
									
								
								_archive/openrpc/generator/hero_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								_archive/openrpc/generator/hero_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import argparse
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.rest_server.python.rest_server_generator import (
 | 
			
		||||
    RestServerGenerator,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do(specs_dir: Path, output: Path):
 | 
			
		||||
    for item in specs_dir.iterdir():
 | 
			
		||||
        if not item.is_dir():
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        actor_name = item.name
 | 
			
		||||
        actor_output_path = output.joinpath(actor_name)
 | 
			
		||||
        actor_output_path.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        print(f"item: {item.as_posix()}")
 | 
			
		||||
        # if item.as_posix() == "generatorexamples/example1/specs/storymanager":
 | 
			
		||||
        #     continue
 | 
			
		||||
        data = parser(path=item.as_posix())
 | 
			
		||||
        # print(f"data: {data}")
 | 
			
		||||
        spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
        server_generator = RestServerGenerator(spec_object, actor_output_path)
 | 
			
		||||
        server_generator.generate()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    arg_parser = argparse.ArgumentParser(description="Hero server and client generator tool.")
 | 
			
		||||
    arg_parser.add_argument(
 | 
			
		||||
        "--specs",
 | 
			
		||||
        type=str,
 | 
			
		||||
        required=True,
 | 
			
		||||
        help="specs directory",
 | 
			
		||||
    )
 | 
			
		||||
    arg_parser.add_argument(
 | 
			
		||||
        "--output",
 | 
			
		||||
        type=str,
 | 
			
		||||
        required=True,
 | 
			
		||||
        help="output directory",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    args = arg_parser.parse_args()
 | 
			
		||||
    do(Path(args.specs), Path(args.output))
 | 
			
		||||
							
								
								
									
										90
									
								
								_archive/openrpc/generator/mdbook/generate_mdbook.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								_archive/openrpc/generator/mdbook/generate_mdbook.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
import argparse
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from ....openrpc.tools import get_pydantic_type, get_return_type, topological_sort
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_models(openrpc_spec: dict) -> str:
 | 
			
		||||
    schema_dict = openrpc_spec["components"]["schemas"]
 | 
			
		||||
    sorted_classes = topological_sort(schema_dict)
 | 
			
		||||
 | 
			
		||||
    env = Environment(loader=FileSystemLoader(script_dir), trim_blocks=True, lstrip_blocks=True)
 | 
			
		||||
    template = env.get_template("templates/mdbook/structs.jinja")
 | 
			
		||||
    model_code = template.render(
 | 
			
		||||
        sorted_classes=sorted_classes,
 | 
			
		||||
        schema_dict=schema_dict,
 | 
			
		||||
        get_pydantic_type=get_pydantic_type,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return model_code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_model(model_name: str, schema: dict) -> str:
 | 
			
		||||
    env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
    template = env.get_template("templates/vlang/struct.jinja")
 | 
			
		||||
    model_code = template.render(model_name=model_name, schema=schema, get_pydantic_type=get_pydantic_type)
 | 
			
		||||
 | 
			
		||||
    return model_code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_api_methods(openrpc_spec: dict) -> str:
 | 
			
		||||
    env = Environment(loader=FileSystemLoader(script_dir), trim_blocks=True, lstrip_blocks=True)
 | 
			
		||||
    template = env.get_template("templates/mdbook/methods.jinja")
 | 
			
		||||
 | 
			
		||||
    code = template.render(
 | 
			
		||||
        spec=openrpc_spec,
 | 
			
		||||
        methods=openrpc_spec.get("methods", []),
 | 
			
		||||
        get_return_type=get_return_type,
 | 
			
		||||
        get_pydantic_type=get_pydantic_type,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main() -> None:
 | 
			
		||||
    parser = argparse.ArgumentParser(description="Generate API code from OpenRPC specification")
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-s",
 | 
			
		||||
        "--spec",
 | 
			
		||||
        help="Path to the specs (expressed in our own V format)",
 | 
			
		||||
        default="~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs",
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-o",
 | 
			
		||||
        "--output",
 | 
			
		||||
        default="/tmp/generator/mdbook",
 | 
			
		||||
        help="Output file path (default: /tmp/generator/mdbook)",
 | 
			
		||||
    )
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    spec_file = os.path.expanduser(args.spec)
 | 
			
		||||
    output_dir = os.path.expanduser(args.output)
 | 
			
		||||
 | 
			
		||||
    if not os.path.isfile(spec_file):
 | 
			
		||||
        print(f"Error: OpenRPC specification file '{spec_file}' does not exist.")
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    with open(spec_file) as file:
 | 
			
		||||
        openrpc_spec = json.load(file)
 | 
			
		||||
 | 
			
		||||
    code_models = generate_models(openrpc_spec)
 | 
			
		||||
    code_methods = generate_api_methods(openrpc_spec)
 | 
			
		||||
 | 
			
		||||
    os.makedirs(os.path.dirname(output_dir), exist_ok=True)
 | 
			
		||||
 | 
			
		||||
    # Write the generated code to a file
 | 
			
		||||
    with open(f"{output_dir}/models.md", "w") as file:
 | 
			
		||||
        file.write(code_models)
 | 
			
		||||
    with open(f"{output_dir}/methods.md", "w") as file:
 | 
			
		||||
        file.write(code_methods)
 | 
			
		||||
 | 
			
		||||
    print(f"Generated API code has been written to {output_dir}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										16
									
								
								_archive/openrpc/generator/mdbook/templates/methods.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								_archive/openrpc/generator/mdbook/templates/methods.jinja
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
## Methods
 | 
			
		||||
 | 
			
		||||
{% for method in methods %}
 | 
			
		||||
- {{ method['name'] }}: {{ method.get('description', '') }}
 | 
			
		||||
  - Parameters:
 | 
			
		||||
      {% for param in method.get('params', []) %}
 | 
			
		||||
        {{ param['name'] }}: {{ get_pydantic_type(param['schema'])}}
 | 
			
		||||
      {% endfor %}
 | 
			
		||||
  
 | 
			
		||||
  - Return Type:
 | 
			
		||||
      {{ get_return_type(method['result']) }}
 | 
			
		||||
  
 | 
			
		||||
  - Example:
 | 
			
		||||
      {{ method.get('examples', [{}])[0] }}
 | 
			
		||||
 | 
			
		||||
{% endfor %}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
# Classes
 | 
			
		||||
 | 
			
		||||
{% for class_name in sorted_classes %}
 | 
			
		||||
- {{ schema_dict[class_name]['title'] }}
 | 
			
		||||
{% for prop_name, prop in schema_dict[class_name]['properties'].items() %}
 | 
			
		||||
  - {{ prop_name }} ({{ get_pydantic_type(prop)}}): {{ prop['description'] }}
 | 
			
		||||
{% endfor %}
 | 
			
		||||
 | 
			
		||||
{% endfor %}
 | 
			
		||||
							
								
								
									
										170
									
								
								_archive/openrpc/generator/model/model_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								_archive/openrpc/generator/model/model_generator.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
			
		||||
from typing import Dict, List, Set
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.code.lang_code_generator import (
 | 
			
		||||
    LangCodeGenerator,
 | 
			
		||||
    PropertyInfo,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.common import (
 | 
			
		||||
    ContentDescriptorObject,
 | 
			
		||||
    ReferenceObject,
 | 
			
		||||
    SchemaObject,
 | 
			
		||||
)
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ModelGenerator:
 | 
			
		||||
    def __init__(self, spec: OpenRPCSpec, lang_code_generator: LangCodeGenerator) -> None:
 | 
			
		||||
        self.spec = spec
 | 
			
		||||
        self.lang_code_generator = lang_code_generator
 | 
			
		||||
        self.processed_objects: Dict[str, Dict[str, str]] = {}
 | 
			
		||||
        self.ordered_objects: List[str] = []
 | 
			
		||||
        self.used_names: Set[str] = set()
 | 
			
		||||
 | 
			
		||||
    def generate_models(self):
 | 
			
		||||
        if not self.spec.components:
 | 
			
		||||
            return ""
 | 
			
		||||
 | 
			
		||||
        schemas = self.spec.components.schemas
 | 
			
		||||
        schemas_path = ["components", "schemas"]
 | 
			
		||||
        for name, schema in schemas.items():
 | 
			
		||||
            self.jsonschema_to_type(
 | 
			
		||||
                path=schemas_path + [name],
 | 
			
		||||
                jsonschema=schema,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        objects_code = ""
 | 
			
		||||
        for val in self.ordered_objects:
 | 
			
		||||
            if val == "":
 | 
			
		||||
                continue
 | 
			
		||||
            objects_code = f"{objects_code}{val}\n\n"
 | 
			
		||||
 | 
			
		||||
        print(f"debugzo4 {objects_code}")
 | 
			
		||||
        return objects_code
 | 
			
		||||
 | 
			
		||||
    def jsonschema_to_type(self, path: List[str], jsonschema: SchemaObject | ReferenceObject) -> str:
 | 
			
		||||
        if isinstance(jsonschema, ReferenceObject):
 | 
			
		||||
            ref: str = jsonschema.ref
 | 
			
		||||
 | 
			
		||||
            ref_schema = self.spec.ref_to_schema(ref)
 | 
			
		||||
            ref_path = ref.split("/")[1:]
 | 
			
		||||
 | 
			
		||||
            if isinstance(ref_schema, ContentDescriptorObject):
 | 
			
		||||
                # TODO: implement
 | 
			
		||||
                raise Exception("unimplemented")
 | 
			
		||||
                # return self.content_descriptor_to_type(ref_path, ref_schema)
 | 
			
		||||
 | 
			
		||||
            return self.jsonschema_to_type(ref_path, ref_schema)
 | 
			
		||||
 | 
			
		||||
        path_str = "/".join([item.lower() for item in path])
 | 
			
		||||
        if path_str in self.processed_objects:
 | 
			
		||||
            return self.processed_objects[path_str]["name"]
 | 
			
		||||
 | 
			
		||||
        type_name = self.type_name_from_path(path)
 | 
			
		||||
 | 
			
		||||
        description = getattr(jsonschema, "description", None)
 | 
			
		||||
        if jsonschema.enum:
 | 
			
		||||
            enum = jsonschema.enum
 | 
			
		||||
            type_code = self.lang_code_generator.generate_enum(enum, type_name)
 | 
			
		||||
            if self.lang_code_generator.is_primitive(type_code):
 | 
			
		||||
                return type_code
 | 
			
		||||
 | 
			
		||||
            self.add_object(path_str, type_code, type_name)
 | 
			
		||||
            return type_name
 | 
			
		||||
 | 
			
		||||
        if jsonschema.type:
 | 
			
		||||
            match jsonschema.type:
 | 
			
		||||
                case "string":
 | 
			
		||||
                    return self.lang_code_generator.string_primitive()
 | 
			
		||||
 | 
			
		||||
                case "integer":
 | 
			
		||||
                    return self.lang_code_generator.integer_primitive()
 | 
			
		||||
 | 
			
		||||
                case "number":
 | 
			
		||||
                    return self.lang_code_generator.number_primitive()
 | 
			
		||||
 | 
			
		||||
                case "array":
 | 
			
		||||
                    if isinstance(jsonschema.items, List):
 | 
			
		||||
                        raise Exception("array of different item types is not supported")
 | 
			
		||||
 | 
			
		||||
                    item_type_name = self.jsonschema_to_type(path + ["item"], jsonschema.items)
 | 
			
		||||
                    return self.lang_code_generator.array_of_type(item_type_name)
 | 
			
		||||
 | 
			
		||||
                case "boolean":
 | 
			
		||||
                    return self.lang_code_generator.bool_primitive()
 | 
			
		||||
 | 
			
		||||
                case "object":
 | 
			
		||||
                    # to prevent cyclic dependencies
 | 
			
		||||
                    self.add_object(path_str, "", type_name)
 | 
			
		||||
 | 
			
		||||
                    properties: Dict[str, PropertyInfo] = {}
 | 
			
		||||
                    for (
 | 
			
		||||
                        property_name,
 | 
			
		||||
                        property_schema,
 | 
			
		||||
                    ) in jsonschema.properties.items():
 | 
			
		||||
                        schema = property_schema
 | 
			
		||||
                        new_path = path + ["properties", property_name]
 | 
			
		||||
                        if isinstance(property_schema, ReferenceObject):
 | 
			
		||||
                            schema = self.spec.ref_to_schema(property_schema.ref)
 | 
			
		||||
                            new_path = property_schema.ref.split("/")[1:]
 | 
			
		||||
 | 
			
		||||
                        property_info = PropertyInfo(
 | 
			
		||||
                            name=property_name,
 | 
			
		||||
                            type_name=self.jsonschema_to_type(new_path, schema),
 | 
			
		||||
                            description=schema.description,
 | 
			
		||||
                            example=schema.example,
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                        properties[property_name] = property_info
 | 
			
		||||
 | 
			
		||||
                    type_code = self.lang_code_generator.generate_object(type_name, properties)
 | 
			
		||||
                    self.add_object(path_str, type_code, type_name)
 | 
			
		||||
                    return type_name
 | 
			
		||||
 | 
			
		||||
                case "null":
 | 
			
		||||
                    return self.lang_code_generator.null_primitive()
 | 
			
		||||
 | 
			
		||||
                case _:
 | 
			
		||||
                    raise Exception(f"type {jsonschema.type} is not supported")
 | 
			
		||||
 | 
			
		||||
        if jsonschema.anyOf:
 | 
			
		||||
            type_names = []
 | 
			
		||||
            for i, item in enumerate(jsonschema.anyOf):
 | 
			
		||||
                type_names.append(self.jsonschema_to_type(path + [f"anyOf{i}"], item))
 | 
			
		||||
 | 
			
		||||
            return self.lang_code_generator.generate_multitype(type_names)
 | 
			
		||||
            # self.add_object(path_str, type_code, type_code)
 | 
			
		||||
            # return type_code
 | 
			
		||||
 | 
			
		||||
        elif jsonschema.oneOf:
 | 
			
		||||
            type_names = []
 | 
			
		||||
            for i, item in enumerate(jsonschema.oneOf):
 | 
			
		||||
                type_names.append(self.jsonschema_to_type(path + [f"oneOf{i}"], item))
 | 
			
		||||
 | 
			
		||||
            return self.lang_code_generator.generate_multitype(type_names)
 | 
			
		||||
            # self.add_object(path_str, type_code, type_code)
 | 
			
		||||
            # return type_code
 | 
			
		||||
 | 
			
		||||
        elif jsonschema.allOf:
 | 
			
		||||
            return self.lang_code_generator.encapsulate_types(jsonschema.allOf)
 | 
			
		||||
            # self.add_object(path_str, type_code, type_code)
 | 
			
		||||
            # return type_name
 | 
			
		||||
 | 
			
		||||
        raise Exception(f"type {jsonschema.type} is not supported")
 | 
			
		||||
 | 
			
		||||
    def add_object(self, path_str: str, type_code: str, type_name: str):
 | 
			
		||||
        self.used_names.add(type_name)
 | 
			
		||||
        self.processed_objects[path_str] = {
 | 
			
		||||
            "code": type_code,
 | 
			
		||||
            "name": type_name,
 | 
			
		||||
        }
 | 
			
		||||
        print(f"debugzo21 {self.processed_objects[path_str]}")
 | 
			
		||||
        self.ordered_objects.append(type_code)
 | 
			
		||||
 | 
			
		||||
    def type_name_from_path(self, path: List[str]) -> str:
 | 
			
		||||
        type_name = ""
 | 
			
		||||
        for item in reversed(path):
 | 
			
		||||
            type_name += item.title() if item.islower() else item
 | 
			
		||||
            if type_name not in self.used_names:
 | 
			
		||||
                return type_name
 | 
			
		||||
 | 
			
		||||
        raise Exception(f"failed to generate unique name from path: {path}")
 | 
			
		||||
							
								
								
									
										14
									
								
								_archive/openrpc/generator/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								_archive/openrpc/generator/readme.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
 | 
			
		||||
## example how to use
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
 | 
			
		||||
import heroserver.openrpc.generator
 | 
			
		||||
 | 
			
		||||
openrpc_spec = generator.openrpc_spec(
 | 
			
		||||
    path="~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
print(openrpc_spec)
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
from typing import Union
 | 
			
		||||
 | 
			
		||||
from fastapi import FastAPI
 | 
			
		||||
from vm_manager__vm_start import vm_start
 | 
			
		||||
 | 
			
		||||
app = FastAPI()
 | 
			
		||||
 | 
			
		||||
#VM WOULD BE AN OBJECT of e.g. a virtual machine description
 | 
			
		||||
 | 
			
		||||
@app.get("/$circleguid/vm_manager/vm")
 | 
			
		||||
def vm_get()-> VM:
 | 
			
		||||
    return {...}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.post("/$circleguid/vm_manager/vm")
 | 
			
		||||
def vm_set()-> bool:
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
@app.delete("/$circleguid/vm_manager/vm")
 | 
			
		||||
def vm_delete()-> bool:
 | 
			
		||||
    ##would use osis to delete this objecc
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.get("/$circleguid/vm_manager/vm_start/{vm_guid}")
 | 
			
		||||
def vm_start(vm_guid: str) -> bool:
 | 
			
		||||
    vm_start(context=context,vm_guid=vm_guid)
 | 
			
		||||
    
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
 | 
			
		||||
def vm_start(context, vm_guid: str) -> bool:
 | 
			
		||||
    #context allows us to see who the user is and which groups the user is
 | 
			
		||||
    #context also gives a logging feature
 | 
			
		||||
    #context is linked to 1 circle
 | 
			
		||||
    #context is linked to a DB (OSIS)
 | 
			
		||||
    
 | 
			
		||||
    #code to be implemented e.g. using DAGU to start a vm
 | 
			
		||||
@@ -0,0 +1,256 @@
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Dict, List, Optional, Union
 | 
			
		||||
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.code.python.python_code_generator import PythonCodeGenerator
 | 
			
		||||
from heroserver.openrpc.generator.model.model_generator import ModelGenerator
 | 
			
		||||
 | 
			
		||||
# Fix the issue by ensuring that the 'object' variable is properly defined and has the expected attributes.
 | 
			
		||||
# The following code will ensure that 'object' is a valid SchemaObject before calling 'print_items'.
 | 
			
		||||
from heroserver.openrpc.model.common import ContentDescriptorObject, ReferenceObject, SchemaObject
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RestServerGenerator:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        spec: OpenRPCSpec,
 | 
			
		||||
        dir: Path,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        if not isinstance(spec, OpenRPCSpec):
 | 
			
		||||
            raise TypeError(f"Expected spec to be of type OpenRPCSpec, got {type(spec)}")
 | 
			
		||||
        if not isinstance(dir, Path):
 | 
			
		||||
            raise TypeError(f"Expected dir to be of type Path, got {type(dir)}")
 | 
			
		||||
 | 
			
		||||
        self.model_generator = ModelGenerator(spec, PythonCodeGenerator())
 | 
			
		||||
        self.spec = spec
 | 
			
		||||
        self.dir = dir
 | 
			
		||||
        self.crud_methods_template = env.get_template("templates/crud_methods.jinja")
 | 
			
		||||
        self.internal_crud_methods_template = env.get_template("templates/internal_crud_methods.jinja")
 | 
			
		||||
        self.internal_crud_mock_methods_template = env.get_template("templates/internal_crud_mock_methods.jinja")
 | 
			
		||||
        self.imports_template = env.get_template("templates/imports.jinja")
 | 
			
		||||
        self.actor_method_template = env.get_template("templates/actor_method.jinja")
 | 
			
		||||
        self.internal_actor_method_template = env.get_template("templates/internal_actor_method.jinja")
 | 
			
		||||
        self.server_template = env.get_template("templates/server.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
        self.dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        self.generate_models()
 | 
			
		||||
        self.generate_crud()
 | 
			
		||||
        self.generate_mock_crud()
 | 
			
		||||
        self.generate_internal_actor_methods()
 | 
			
		||||
        self.generate_openapi()
 | 
			
		||||
        self.generate_openapi_mock()
 | 
			
		||||
        self.generate_server()
 | 
			
		||||
 | 
			
		||||
        print(f"Generated API code has been written to {self.dir}")
 | 
			
		||||
 | 
			
		||||
    def generate_server(self):
 | 
			
		||||
        code = self.server_template.render()
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("server.py")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(code)
 | 
			
		||||
 | 
			
		||||
    def generate_openapi(self):
 | 
			
		||||
        imports = self.imports_template.render(import_crud=True, import_models=True)
 | 
			
		||||
        app_init = "app = FastAPI()\n\n"
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
            methods += self.crud_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
 | 
			
		||||
 | 
			
		||||
        for method in self.spec.methods:
 | 
			
		||||
            if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
 | 
			
		||||
 | 
			
		||||
            return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
 | 
			
		||||
 | 
			
		||||
            function_name = method.name.lower().replace(".", "_")
 | 
			
		||||
            imports += f"from {function_name}_internal import {function_name}_internal\n"
 | 
			
		||||
            methods += (
 | 
			
		||||
                self.actor_method_template.render(
 | 
			
		||||
                    rest_server_generator=self,
 | 
			
		||||
                    function_name=function_name,
 | 
			
		||||
                    method_params=params,
 | 
			
		||||
                    method_result=return_type,
 | 
			
		||||
                )
 | 
			
		||||
                + "\n\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("open_api.py")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{app_init}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_openapi_mock(self):
 | 
			
		||||
        imports = self.imports_template.render(mock=True, import_crud=True, import_models=True)
 | 
			
		||||
        app_init = "app = FastAPI()\n\n"
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
            methods += self.crud_methods_template.render(mock=True, variable_name=variable_name, type_name=type_name) + "\n\n"
 | 
			
		||||
 | 
			
		||||
        for method in self.spec.methods:
 | 
			
		||||
            if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
 | 
			
		||||
 | 
			
		||||
            return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
 | 
			
		||||
 | 
			
		||||
            function_name = method.name.lower().replace(".", "_")
 | 
			
		||||
            imports += f"from {function_name}_internal import {function_name}_internal\n"
 | 
			
		||||
            methods += (
 | 
			
		||||
                self.actor_method_template.render(
 | 
			
		||||
                    mock=True,
 | 
			
		||||
                    rest_server_generator=self,
 | 
			
		||||
                    function_name=function_name,
 | 
			
		||||
                    method_params=params,
 | 
			
		||||
                    method_result=return_type,
 | 
			
		||||
                )
 | 
			
		||||
                + "\n\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("open_api_mock.py")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{app_init}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_models(self):
 | 
			
		||||
        imports = self.imports_template.render()
 | 
			
		||||
        code = self.model_generator.generate_models()
 | 
			
		||||
        path = self.dir.joinpath("models.py")
 | 
			
		||||
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{code}\n")
 | 
			
		||||
 | 
			
		||||
    def generate_crud(self):
 | 
			
		||||
        imports = self.imports_template.render(import_models=True)
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
            methods += self.internal_crud_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("crud.py")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_mock_crud(self):
 | 
			
		||||
        imports = self.imports_template.render(import_models=True)
 | 
			
		||||
        imports += "from heroserver.openrpc.tools import create_example_object"
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.spec.get_root_objects()[path_str]
 | 
			
		||||
 | 
			
		||||
            if isinstance(object, SchemaObject):
 | 
			
		||||
                print_items(object)
 | 
			
		||||
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
 | 
			
		||||
            methods += self.internal_crud_mock_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("crud_mock.py")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_internal_actor_methods(self):
 | 
			
		||||
        imports = self.imports_template.render(import_models=True)
 | 
			
		||||
        for method in self.spec.methods:
 | 
			
		||||
            function_name = method.name.lower().replace(".", "_") + "_internal"
 | 
			
		||||
            file_path = self.dir.joinpath(f"{function_name}.py")
 | 
			
		||||
            if file_path.exists():
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
 | 
			
		||||
 | 
			
		||||
            return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
 | 
			
		||||
 | 
			
		||||
            code = self.internal_actor_method_template.render(
 | 
			
		||||
                rest_server_generator=self,
 | 
			
		||||
                function_name=function_name,
 | 
			
		||||
                method_params=params,
 | 
			
		||||
                method_result=return_type,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            with open(file_path, "w") as file:
 | 
			
		||||
                file.write(f"{imports}\n\n{code}")
 | 
			
		||||
 | 
			
		||||
    def get_method_params(self, method_params: Dict[str, str]) -> str:
 | 
			
		||||
        return ", ".join([f"{param_name}: {param_type}" for param_name, param_type in method_params.items()])
 | 
			
		||||
 | 
			
		||||
    def method_result_return_type(
 | 
			
		||||
        self,
 | 
			
		||||
        path: List[str],
 | 
			
		||||
        method_result: Optional[Union[ContentDescriptorObject, ReferenceObject]],
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        if not method_result:
 | 
			
		||||
            type_name = ""
 | 
			
		||||
 | 
			
		||||
        if isinstance(method_result, ContentDescriptorObject):
 | 
			
		||||
            schema = method_result.schema
 | 
			
		||||
            type_name = self.model_generator.jsonschema_to_type(path, schema)
 | 
			
		||||
 | 
			
		||||
        elif isinstance(method_result, ReferenceObject):
 | 
			
		||||
            type_name = self.model_generator.jsonschema_to_type(path, method_result)
 | 
			
		||||
 | 
			
		||||
        return type_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_items(schema_object, depth=0):
 | 
			
		||||
    print(f"prito {schema_object.items}")
 | 
			
		||||
    indent = "    " * depth
 | 
			
		||||
    if isinstance(schema_object.items, list):
 | 
			
		||||
        for item in schema_object.items:
 | 
			
		||||
            print(f"{indent}Item: {item}")
 | 
			
		||||
            if isinstance(item, SchemaObject):
 | 
			
		||||
                print_items(item, depth + 1)
 | 
			
		||||
            print(f"{indent}Example: {item.example}")
 | 
			
		||||
    elif isinstance(schema_object.items, SchemaObject):
 | 
			
		||||
        print(f"{indent}Item: {schema_object.items}")
 | 
			
		||||
        print_items(schema_object.items, depth + 1)
 | 
			
		||||
        print(f"{indent}Example: {schema_object.items.example}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    data = parser(path="~/code/git.threefold.info/hero/hero_server_python/baobabspecs")
 | 
			
		||||
 | 
			
		||||
    spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
    server_generator = RestServerGenerator(spec_object, Path("/tmp/rest2"))
 | 
			
		||||
    server_generator.generate()
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
@app.post("/$circleguid/{{function_name}}")
 | 
			
		||||
def {{ function_name }}(circleguid: int, {{ rest_server_generator.get_method_params(method_params) }}){% if method_result %} -> {{ method_result }}{% endif %}:
 | 
			
		||||
    {% if mock %}
 | 
			
		||||
        return {{function_name}}_internal_mock(context, circleguid, {{', '.join(method_params.keys())}})
 | 
			
		||||
    {% else %}
 | 
			
		||||
        return {{function_name}}_internal(context, circleguid, {{', '.join(method_params.keys())}})
 | 
			
		||||
    {% endif %}
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
{% if mock %}
 | 
			
		||||
    {% set suffix = '_mock' %}
 | 
			
		||||
{% else %}
 | 
			
		||||
    {% set suffix = '' %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
@app.get("/{circleguid}/{{variable_name}}_manager{{suffix}}/{{variable_name}}/{id}")
 | 
			
		||||
def {{variable_name}}_get(circleguid: int, id: str)-> {{type_name}}:
 | 
			
		||||
    return {{variable_name}}_get_internal{{suffix}}(circleguid, id)
 | 
			
		||||
 | 
			
		||||
@app.post("/{circleguid}/{{variable_name}}_manager{{suffix}}/{{variable_name}}")
 | 
			
		||||
def {{variable_name}}_set(circleguid: int, {{variable_name}}: {{type_name}})-> bool:
 | 
			
		||||
    return {{variable_name}}_set_internal{{suffix}}(circleguid, {{variable_name}})
 | 
			
		||||
 | 
			
		||||
@app.delete("/{circleguid}/{{variable_name}}_manager{{suffix}}/{{variable_name}}/{id}")
 | 
			
		||||
def {{variable_name}}_delete(circleguid: int, id: str)-> bool:
 | 
			
		||||
    return {{variable_name}}_delete_internal{{suffix}}(circleguid, id)
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
{% if mock %}
 | 
			
		||||
    {% set suffix = '_mock' %}
 | 
			
		||||
{% else %}
 | 
			
		||||
    {% set suffix = '' %}
 | 
			
		||||
{% endif %}
 | 
			
		||||
from fastapi import FastAPI
 | 
			
		||||
from pydantic import BaseModel, Field
 | 
			
		||||
from typing import List
 | 
			
		||||
from enum import Enum
 | 
			
		||||
{% if import_models %}from models import *{% endif %}
 | 
			
		||||
{% if import_crud %}from crud{{suffix}} import *{% endif %}
 | 
			
		||||
{% if import_openapi %}from open_api import *{% endif %}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
from typing import List, Optional, Dict, Union
 | 
			
		||||
from enum import Enum
 | 
			
		||||
 | 
			
		||||
def {{function_name}}(context, circleguid: int, {{rest_server_generator.get_method_params(method_params)}}) -> {{method_result}}:
 | 
			
		||||
    #context allows us to see who the user is and which groups the user is
 | 
			
		||||
    #context also gives a logging feature
 | 
			
		||||
    #context is linked to 1 circle
 | 
			
		||||
    #context is linked to a DB (OSIS)
 | 
			
		||||
    pass
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
def {{variable_name}}_get_internal(circleguid: int, id: str) -> {{type_name}}:
 | 
			
		||||
    return {{type_name}}()
 | 
			
		||||
 | 
			
		||||
def {{variable_name}}_set_internal(circleguid: int, {{variable_name}}: {{type_name}})-> bool:
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
def {{variable_name}}_delete_internal(circleguid: int, id: str)-> bool:
 | 
			
		||||
    ##would use osis to delete this objecc
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
def {{variable_name}}_get_internal_mock(circleguid: int, id: str) -> {{type_name}}:
 | 
			
		||||
    return create_example_object({{type_name}})
 | 
			
		||||
 | 
			
		||||
def {{variable_name}}_set_internal_mock(circleguid: int, {{variable_name}}: {{type_name}})-> bool:
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
def {{variable_name}}_delete_internal_mock(circleguid: int, id: str)-> bool:
 | 
			
		||||
    return True
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
import uvicorn
 | 
			
		||||
from open_api import app
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    uvicorn.run(app, host="0.0.0.0", port=8000)
 | 
			
		||||
@@ -0,0 +1,169 @@
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Dict, List, Optional, Union
 | 
			
		||||
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.generator.actor.vlang.vlang_code_generator import VlangGenerator
 | 
			
		||||
from heroserver.openrpc.generator.model_generator import ModelGenerator
 | 
			
		||||
from heroserver.openrpc.model.common import ContentDescriptorObject, ReferenceObject
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RestServerGenerator:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        spec: OpenRPCSpec,
 | 
			
		||||
        dir: Path,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.lang_code_generator = VlangGenerator()
 | 
			
		||||
        self.model_generator = ModelGenerator(spec, VlangGenerator())
 | 
			
		||||
        self.spec = spec
 | 
			
		||||
        self.dir = dir
 | 
			
		||||
        self.crud_methods_template = env.get_template("templates/crud_methods.jinja")
 | 
			
		||||
        self.internal_crud_methods_template = env.get_template("templates/internal_crud_methods.jinja")
 | 
			
		||||
        self.imports_template = env.get_template("templates/imports.jinja")
 | 
			
		||||
        self.actor_method_template = env.get_template("templates/actor_method.jinja")
 | 
			
		||||
        self.internal_actor_method_template = env.get_template("templates/internal_actor_method.jinja")
 | 
			
		||||
        self.server_template = env.get_template("templates/server.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
        self.dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        self.generate_models()
 | 
			
		||||
        self.generate_crud()
 | 
			
		||||
        self.generate_internal_actor_methods()
 | 
			
		||||
        self.generate_openapi()
 | 
			
		||||
        self.generate_server()
 | 
			
		||||
 | 
			
		||||
        print(f"Generated API code has been written to {self.dir}")
 | 
			
		||||
 | 
			
		||||
    def generate_server(self):
 | 
			
		||||
        imports = self.imports_template.render(import_vweb=True)
 | 
			
		||||
        code = self.server_template.render()
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("server.v")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{code}")
 | 
			
		||||
 | 
			
		||||
    def generate_openapi(self):
 | 
			
		||||
        imports = self.imports_template.render(import_vweb=True)
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
            methods += self.crud_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
 | 
			
		||||
 | 
			
		||||
        for method in self.spec.methods:
 | 
			
		||||
            if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
 | 
			
		||||
 | 
			
		||||
            return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
 | 
			
		||||
 | 
			
		||||
            function_name = method.name.lower().replace(".", "_")
 | 
			
		||||
            methods += (
 | 
			
		||||
                self.actor_method_template.render(
 | 
			
		||||
                    rest_server_generator=self,
 | 
			
		||||
                    function_name=function_name,
 | 
			
		||||
                    method_params=params,
 | 
			
		||||
                    method_result=return_type,
 | 
			
		||||
                )
 | 
			
		||||
                + "\n\n"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("open_api.v")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_models(self):
 | 
			
		||||
        imports = self.imports_template.render()
 | 
			
		||||
        code = self.model_generator.generate_models()
 | 
			
		||||
        path = self.dir.joinpath("models.v")
 | 
			
		||||
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{code}\n")
 | 
			
		||||
 | 
			
		||||
    def generate_crud(self):
 | 
			
		||||
        imports = self.imports_template.render(import_models=True)
 | 
			
		||||
        methods = ""
 | 
			
		||||
        for path_str in self.model_generator.spec.get_root_objects().keys():
 | 
			
		||||
            object = self.model_generator.processed_objects[path_str]
 | 
			
		||||
            if object["code"] == "":
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            type_name = object["name"]
 | 
			
		||||
            variable_name = type_name.lower()
 | 
			
		||||
            methods += self.internal_crud_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
 | 
			
		||||
 | 
			
		||||
        path = self.dir.joinpath("crud.v")
 | 
			
		||||
        with open(path, "w") as file:
 | 
			
		||||
            file.write(f"{imports}\n\n{methods}")
 | 
			
		||||
 | 
			
		||||
    def generate_internal_actor_methods(self):
 | 
			
		||||
        imports = self.imports_template.render(import_models=True)
 | 
			
		||||
        for method in self.spec.methods:
 | 
			
		||||
            function_name = method.name.lower().replace(".", "_") + "_internal"
 | 
			
		||||
            file_path = self.dir.joinpath(f"{function_name}.v")
 | 
			
		||||
            if file_path.exists():
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            params: Dict[str, str] = {}
 | 
			
		||||
            for param in method.params:
 | 
			
		||||
                params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
 | 
			
		||||
 | 
			
		||||
            return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
 | 
			
		||||
 | 
			
		||||
            code = self.internal_actor_method_template.render(
 | 
			
		||||
                rest_server_generator=self,
 | 
			
		||||
                function_name=function_name,
 | 
			
		||||
                method_params=params,
 | 
			
		||||
                method_result=return_type,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            with open(file_path, "w") as file:
 | 
			
		||||
                file.write(f"{imports}\n\n{code}")
 | 
			
		||||
 | 
			
		||||
    def get_method_params(self, method_params: Dict[str, str]) -> str:
 | 
			
		||||
        return ", ".join([f"{param_name} {param_type}" for param_name, param_type in method_params.items()])
 | 
			
		||||
 | 
			
		||||
    def method_result_return_type(
 | 
			
		||||
        self,
 | 
			
		||||
        path: List[str],
 | 
			
		||||
        method_result: Optional[Union[ContentDescriptorObject, ReferenceObject]],
 | 
			
		||||
    ) -> str:
 | 
			
		||||
        if not method_result:
 | 
			
		||||
            type_name = ""
 | 
			
		||||
 | 
			
		||||
        if isinstance(method_result, ContentDescriptorObject):
 | 
			
		||||
            schema = method_result.schema
 | 
			
		||||
            type_name = self.model_generator.jsonschema_to_type(path, schema)
 | 
			
		||||
 | 
			
		||||
        elif isinstance(method_result, ReferenceObject):
 | 
			
		||||
            type_name = self.model_generator.jsonschema_to_type(path, method_result)
 | 
			
		||||
 | 
			
		||||
        return type_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    from heroserver.openrpc.generator.model_generator import ModelGenerator
 | 
			
		||||
    from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
    data = parser(path="/root/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs/storymanager")
 | 
			
		||||
 | 
			
		||||
    spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
    server_generator = RestServerGenerator(spec_object, Path("/tmp/rest3"))
 | 
			
		||||
    server_generator.generate()
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
@['/:circleguid/{{function_name}}'; post]
 | 
			
		||||
pub fn (mut v_server_app VServerApp) {{ function_name }}(circleguid int) vweb.Result{
 | 
			
		||||
    body := json2.raw_decode(v_server_app.req.data)!.as_map()
 | 
			
		||||
    {% for param_name, param_tpe in method_params.items() %}
 | 
			
		||||
    {% if rest_server_generator.lang_code_generator.is_primitive(param_type) %}
 | 
			
		||||
    {{param_name}} := body['{{param_name}}'].{{param_type}}()
 | 
			
		||||
    {% else %}
 | 
			
		||||
    {{param_name}} := json2.decode[{{param_type}}](body['{{param_name}}'].json_str()) or {
 | 
			
		||||
        v_server_app.set_status(400, '')
 | 
			
		||||
        return v_server_app.text('HTTP 400: Bad Request')
 | 
			
		||||
    }
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    res := {{function_name}}_internal(context, circleguid, {{', '.join(method_params.keys())}}) or {
 | 
			
		||||
        v_server_app.set_status(500, '')
 | 
			
		||||
        return v_server_app.text('HTTP 500: Internal Server Error')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return v_server_app.json(res)
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
@['/:circleguid/{{variable_name}}_manager/{{variable_name}}/:id'; get]
 | 
			
		||||
pub fn (mut v_server_app VServerApp) {{variable_name}}_get(circleguid int, id str) vweb.Result{
 | 
			
		||||
    res := {{variable_name}}_get_internal(circleguid, id) or {
 | 
			
		||||
        v_server_app.set_status(500, '')
 | 
			
		||||
        return v_server_app.text('HTTP 500: Internal Server Error')
 | 
			
		||||
    }
 | 
			
		||||
    return v_server_app.json(res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@['/:circleguid/{{variable_name}}_manager/{{variable_name}}'; post]
 | 
			
		||||
pub fn (mut v_server_app VServerApp) {{variable_name}}_set(circleguid int) vweb.Result{
 | 
			
		||||
    {{variable_name}} := json2.decode[{{type_name}}](v_server_app.req.data) or {
 | 
			
		||||
        v_server_app.set_status(400, '')
 | 
			
		||||
        return v_server_app.text('HTTP 400: Bad Request')
 | 
			
		||||
    }
 | 
			
		||||
    res := {{variable_name}}_set_internal(circleguid, {{variable_name}})or {
 | 
			
		||||
        v_server_app.set_status(500, '')
 | 
			
		||||
        return v_server_app.text('HTTP 500: Internal Server Error')
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return v_server_app.json(res)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@['/:circleguid/{{variable_name}}_manager/{{variable_name}}/:id'; delete]
 | 
			
		||||
pub fn (mut v_server_app VServerApp) {{variable_name}}_delete(circleguid int, id str) vweb.Result{
 | 
			
		||||
    ##would use osis to delete this objecc
 | 
			
		||||
    res := {{variable_name}}_delete_internal(circleguid, id) or {
 | 
			
		||||
        v_server_app.set_status(500, '')
 | 
			
		||||
        return v_server_app.text('HTTP 500: Internal Server Error')
 | 
			
		||||
    }
 | 
			
		||||
    return v_server_app.json(res)
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
module main
 | 
			
		||||
 | 
			
		||||
import x.json2
 | 
			
		||||
{% if import_vweb %}import vweb{% endif %}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
module main
 | 
			
		||||
 | 
			
		||||
import freeflowuniverse.crystallib.context
 | 
			
		||||
 | 
			
		||||
pub fn {{function_name}}(ctx context.Context, circleguid int, {{rest_server_generator.get_method_params(method_params)}}) !{{method_result}}{
 | 
			
		||||
    // context allows us to see who the user is and which groups the user is
 | 
			
		||||
    // context also gives a logging feature
 | 
			
		||||
    // context is linked to 1 circle
 | 
			
		||||
    // context is linked to a DB (OSIS)
 | 
			
		||||
    return 0
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
pub fn {{variable_name}}_get_internal(db &backend.Indexer, circleguid int, id string) !{{type_name}}{
 | 
			
		||||
    json_str := db.get(id, RootObject{
 | 
			
		||||
        name: '{{type_name}}'
 | 
			
		||||
    })!
 | 
			
		||||
    
 | 
			
		||||
    return json.decode({{type_name}}, json_str)!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn {{variable_name}}_set_internal(db &backend.Indexer, circleguid int, id string, {{variable_name}} {{type_name}}) !{
 | 
			
		||||
    if id != ''{
 | 
			
		||||
        db.set(RootObject{
 | 
			
		||||
            id: id
 | 
			
		||||
            name: '{{type_name}}'
 | 
			
		||||
        })!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    db.new(RootObject{
 | 
			
		||||
        name: '{{type_name}}'
 | 
			
		||||
    })!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn {{variable_name}}_delete_internal(db &backend.Indexer, circleguid int, id string) !{
 | 
			
		||||
    db.delete(id, RootObject{
 | 
			
		||||
        name: '{{type_name}}'
 | 
			
		||||
    })!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
module main
 | 
			
		||||
 | 
			
		||||
import freeflowuniverse.crystallib.baobab.backend
 | 
			
		||||
import freeflowuniverse.crystallib.clients.redisclient
 | 
			
		||||
import net.http
 | 
			
		||||
 | 
			
		||||
struct RPCServer {
 | 
			
		||||
pub mut:
 | 
			
		||||
    backend &backend.Backend
 | 
			
		||||
    redis &redisclient.Redis
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    rpc_server := new_server() or{
 | 
			
		||||
        eprintln('failed to create server: ${err}')
 | 
			
		||||
        exit(1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rpc_server.listen_and_serve(8000) or {
 | 
			
		||||
        eprintln('server error: ${err}')
 | 
			
		||||
        exit(1)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn new_server() !RPCServer{
 | 
			
		||||
     db := start_new_backend_conn()!
 | 
			
		||||
    redis_client := redisclient.new(['localhost:6379'])!
 | 
			
		||||
    return RPCServer{
 | 
			
		||||
        backend: &db
 | 
			
		||||
        redis: &redis_client
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut s RPCServer) listen_and_serve(port int) !{
 | 
			
		||||
    mut server := &http.Server{
 | 
			
		||||
        addr: 'localhost:${port}'
 | 
			
		||||
        handler: s.handler
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    server.listen_and_serve()!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn (mut s RPCServer) handler(req http.Request) http.Response{
 | 
			
		||||
    if req.method != .post || req.url != '/'{
 | 
			
		||||
        return http.Response{
 | 
			
		||||
            status: 400
 | 
			
		||||
            status_msg: 'Bad Request. invalid method or path'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body := req.data
 | 
			
		||||
    id := id := jsonrpc.decode_request_id(body) or { 
 | 
			
		||||
        return http.Response(status: 400, status_msg: 'Bad Request. Cannot decode request.id ${msg}') 
 | 
			
		||||
    }
 | 
			
		||||
    method := jsonrpc.jsonrpcrequest_decode_method(body) or { 
 | 
			
		||||
        return http.Response(status: 400, status_msg: 'Bad Request. Cannot decode request.method ${msg}') 
 | 
			
		||||
    }
 | 
			
		||||
    params_str := jsonrpc.request_params(body) or {
 | 
			
		||||
        return http.Response(status: 400, status_msg: 'Bad Request. Cannot decode request.params ${msg}')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s.handle_rpc(id, method, params_str)
 | 
			
		||||
 | 
			
		||||
    response_body := s.redis.brpop([id]) or {
 | 
			
		||||
        return http.Response(status: 500, status_msg: 'Internal Server Error: Serer timed-out while waiting for a response')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return http.Response{
 | 
			
		||||
        status: 200
 | 
			
		||||
        body: response_body
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								_archive/openrpc/generator/server/templates/method.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								_archive/openrpc/generator/server/templates/method.jinja
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
@['/:{{function_name}}'; post]
 | 
			
		||||
pub fn (mut app App) {{ function_name }}() vweb.Result{
 | 
			
		||||
    body := json2.raw_decode(app.req.data)!.as_map()
 | 
			
		||||
    {% for param_name, param_tpe in method_params.items() %}
 | 
			
		||||
    {% if rest_server_generator.lang_code_generator.is_primitive(param_type) %}
 | 
			
		||||
    {{param_name}} := body['{{param_name}}'].{{param_type}}()
 | 
			
		||||
    {% else %}
 | 
			
		||||
    {{param_name}} := json2.decode[{{param_type}}](body['{{param_name}}'].json_str()) or {
 | 
			
		||||
        app.set_status(400, 'Bad Request: ${err}')
 | 
			
		||||
        return v_server_app.text('HTTP 400: Bad Request')
 | 
			
		||||
    }
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    res := {{function_name}}_internal({{', '.join(method_params.keys())}}) or {
 | 
			
		||||
        app.set_status(500, '')
 | 
			
		||||
        return app.text('HTTP 500: Internal Server Error')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return app.json(res)
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
module main
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub fn {{function_name}}({{server_generator.get_method_params(method_params)}}) !{{method_result}}{
 | 
			
		||||
    panic('to be implemented')
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								_archive/openrpc/generator/server/templates/server.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								_archive/openrpc/generator/server/templates/server.jinja
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
module main
 | 
			
		||||
 | 
			
		||||
import vweb
 | 
			
		||||
 | 
			
		||||
struct App {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    app := &App{}
 | 
			
		||||
    port := 8080
 | 
			
		||||
    vweb.run(app, port)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								_archive/openrpc/generator/server/vlang.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								_archive/openrpc/generator/server/vlang.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
from jinja2 import Environment, FileSystemLoader
 | 
			
		||||
 | 
			
		||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
 | 
			
		||||
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
env = Environment(loader=FileSystemLoader(script_dir))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServerGenerator:
 | 
			
		||||
    def __init__(self, spec: OpenRPCSpec, dir: Path):
 | 
			
		||||
        self.spec = spec
 | 
			
		||||
        self.dir = dir
 | 
			
		||||
 | 
			
		||||
        self.server_template = env.get_template("templates/server.jinja")
 | 
			
		||||
 | 
			
		||||
    def generate(self):
 | 
			
		||||
        self.dir.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
        self.generate_server()
 | 
			
		||||
        self.generate_models()
 | 
			
		||||
        self.generate_methods()
 | 
			
		||||
 | 
			
		||||
    def generate_server(self):
 | 
			
		||||
        code = self.server_template.render()
 | 
			
		||||
        server_file_path = self.dir.joinpath("server.v")
 | 
			
		||||
 | 
			
		||||
        with open(server_file_path, "w") as file:
 | 
			
		||||
            file.write(f"{code}")
 | 
			
		||||
 | 
			
		||||
    def generate_models():
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def generate_methods():
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    from heroserver.openrpc.parser.parser import parser
 | 
			
		||||
 | 
			
		||||
    # from heroserver.openrpc.generator.model_generator import ModelGenerator
 | 
			
		||||
 | 
			
		||||
    data = parser(path="/root/code/git.threefold.info/hero_server/generatorexamples/mycelium_openrpc.yaml")
 | 
			
		||||
 | 
			
		||||
    spec_object = OpenRPCSpec.load(data)
 | 
			
		||||
    server_generator = ServerGenerator(spec_object, Path("/tmp/server3"))
 | 
			
		||||
    server_generator.generate()
 | 
			
		||||
		Reference in New Issue
	
	Block a user