...
This commit is contained in:
23
_archive/openrpc/__init__.py
Normal file
23
_archive/openrpc/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from heroserver.openrpc.factory import openrpc_dict, openrpc_spec, openrpc_spec_write
|
||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
|
||||
|
||||
|
||||
def init_openrpc_dict(path: str = "") -> dict:
|
||||
"""
|
||||
return openrpc dict
|
||||
"""
|
||||
return openrpc_dict(path=path)
|
||||
|
||||
|
||||
def init_openrpc_spec_write(path: str = "", dest: str = "") -> str:
|
||||
"""
|
||||
parse & write the specs to the destination, the path will be ${destination}/openrpc_spec.json" and .../openrpc_spec.yaml"
|
||||
"""
|
||||
return openrpc_spec_write(path=path, dest=dest)
|
||||
|
||||
|
||||
def init_openrpc_spec(path: str = "") -> OpenRPCSpec:
|
||||
"""
|
||||
return openrpc object
|
||||
"""
|
||||
return openrpc_spec(path=path)
|
58
_archive/openrpc/factory.py
Normal file
58
_archive/openrpc/factory.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import yaml # type: ignore
|
||||
|
||||
from heroserver.openrpc.model.openrpc_spec import (
|
||||
OpenRPCSpec,
|
||||
)
|
||||
from heroserver.openrpc.parser.parser import parser
|
||||
|
||||
|
||||
def openrpc_spec_write(path: str = "", dest: str = "") -> str:
|
||||
"""
|
||||
parse & write the specs
|
||||
dest is the path where we write the openrpc specs
|
||||
returns filename = f"{dest}/openrpc_spec.json"
|
||||
"""
|
||||
data = openrpc_dict(path=path)
|
||||
|
||||
out = json.dumps(data, indent=2)
|
||||
# print(out)
|
||||
|
||||
dest = os.path.expanduser(dest)
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
|
||||
filename = f"{dest}/openrpc_spec.json"
|
||||
# Write the spec to the file
|
||||
with open(filename, "w") as f:
|
||||
f.write(out)
|
||||
print(f"OpenRPC specification (JSON) has been written to: {filename}")
|
||||
|
||||
yaml_filename = f"{dest}/openrpc_spec.yaml"
|
||||
with open(yaml_filename, "w") as f:
|
||||
yaml.dump(data, f, sort_keys=False)
|
||||
print(f"OpenRPC specification (YAML) has been written to: {yaml_filename}")
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def openrpc_spec(path: str = "") -> OpenRPCSpec:
|
||||
"""
|
||||
return openrpc object starting from spec path
|
||||
this is our python representation of OpenRPCSpec
|
||||
"""
|
||||
data = openrpc_dict(path=path)
|
||||
|
||||
spec_object = OpenRPCSpec.load(data)
|
||||
|
||||
return spec_object
|
||||
|
||||
|
||||
def openrpc_dict(path: str = "") -> dict:
|
||||
"""
|
||||
return openrpc dict starting from spec path
|
||||
"""
|
||||
data = parser(path=path)
|
||||
|
||||
return data
|
91
_archive/openrpc/factory_model.py
Normal file
91
_archive/openrpc/factory_model.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
from heroserver.openrpc.factory import openrpc_dict, openrpc_spec, openrpc_spec_write
|
||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
|
||||
|
||||
|
||||
class OpenRPCFactory:
|
||||
def __init__(self, generation_path: str, spec_path: str):
|
||||
"""
|
||||
Initialize the OpenRPCFactory with a generation path and a spec path.
|
||||
|
||||
:param generation_path: The path where the generation will occur.
|
||||
:param spec_path: The path to the OpenRPC specification (in vlang format).
|
||||
"""
|
||||
import os.path
|
||||
|
||||
self.actors: Dict[str, OpenRPCActor] = {}
|
||||
self.generation_path: str = os.path.expanduser(generation_path)
|
||||
self.spec_path: str = os.path.expanduser(spec_path)
|
||||
|
||||
def add_actor(self, actor: "OpenRPCActor"):
|
||||
self.actors[actor.name] = actor
|
||||
|
||||
def get_actor(self, name: str) -> Optional["OpenRPCActor"]:
|
||||
return self.actors.get(name)
|
||||
|
||||
def remove_actor(self, name: str) -> None:
|
||||
self.actors.pop(name, None)
|
||||
|
||||
def scan(self):
|
||||
for subdir in os.listdir(self.spec_path):
|
||||
subdir_path = os.path.join(self.spec_path, subdir)
|
||||
if os.path.isdir(subdir_path):
|
||||
actor = OpenRPCActor(name=subdir, path_ourspec=subdir_path, parent=self)
|
||||
self.add_actor(actor)
|
||||
|
||||
|
||||
class OpenRPCActor:
|
||||
def __init__(self, name: str, path_ourspec: str, parent: OpenRPCFactory):
|
||||
self.name: str = name
|
||||
self.path_ourspec: str = path_ourspec # the directory where we parse & generate
|
||||
self.path_openrpc: str = os.path.join(parent.generation_path, self.name) # the file which represents openrpc spec
|
||||
self.parent = parent
|
||||
|
||||
self.openrpc_spec: OpenRPCSpec = openrpc_spec(path=path_ourspec)
|
||||
|
||||
def openrpc_dict(self) -> dict:
|
||||
return openrpc_dict(path=self.path_ourspec)
|
||||
|
||||
def openrpc_spec_write(self) -> dict:
|
||||
return openrpc_spec_write(path=self.path_ourspec, dest=self.path_openrpc)
|
||||
|
||||
def openrpc_spec_yaml_path(self) -> str:
|
||||
yaml_path = os.path.join(self.path_openrpc, "openrpc_spec.yaml")
|
||||
if not os.path.exists(yaml_path):
|
||||
self.openrpc_spec_write()
|
||||
return yaml_path
|
||||
|
||||
def openrpc_spec_json_path(self) -> str:
|
||||
json_path = os.path.join(self.path_openrpc, "openrpc_spec.json")
|
||||
if not os.path.exists(json_path):
|
||||
self.openrpc_spec_write()
|
||||
return json_path
|
||||
|
||||
def generate_rest_server(self):
|
||||
from heroserver.openrpc.generator.rest_server.python.rest_server_generator import RestServerGenerator
|
||||
|
||||
rest_server_generator = RestServerGenerator(self.openrpc_spec, Path(self.path_openrpc))
|
||||
rest_server_generator.generate()
|
||||
|
||||
|
||||
def new(generation_path: str, spec_path: str) -> OpenRPCFactory:
|
||||
"""
|
||||
Create a new OpenRPCFactory and return OpenRPCActors, starting from a path.
|
||||
|
||||
:param generation_path: The path where the generation will occur.
|
||||
:param spec_path: The path to the OpenRPC specification.
|
||||
:return: An instance of OpenRPCFactory with actors initialized.
|
||||
"""
|
||||
factory = OpenRPCFactory(generation_path=generation_path, spec_path=spec_path)
|
||||
factory.scan()
|
||||
return factory
|
||||
|
||||
|
||||
# Usage example:
|
||||
# spec = OpenRPCSpec(...) # Create an OpenRPCSpec instance
|
||||
# actor = OpenRPCActor("MyActor", "/path/to/actor", spec, "/path/to/openrpc.json")
|
||||
# actors = OpenRPCActors()
|
||||
# actors.add_actor(actor)
|
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()
|
55
_archive/openrpc/model/__init__.py
Normal file
55
_archive/openrpc/model/__init__.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import json
|
||||
|
||||
import yaml # type: ignore
|
||||
|
||||
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
|
||||
|
||||
# def decode_openrpc(yaml_string: str) -> dict:
|
||||
# # TODO:
|
||||
# pass
|
||||
|
||||
|
||||
# def encode_openrpc(openrpc_object: dict) -> str:
|
||||
# # TODO:
|
||||
# pass
|
||||
|
||||
|
||||
def decode_openrpc_yaml(yaml_string: str) -> OpenRPCSpec:
|
||||
# Parse YAML string into a Python dict and then convert it into an OpenRPCObject using Pydantic
|
||||
data = yaml.safe_load(yaml_string)
|
||||
return OpenRPCSpec.load(data)
|
||||
|
||||
|
||||
def encode_openrpc_yaml(openrpc_object: OpenRPCSpec) -> str:
|
||||
# Convert the OpenRPCObject instance to a dictionary and then dump it to a YAML string
|
||||
return yaml.dump(openrpc_object.__dict__, sort_keys=False, allow_unicode=True)
|
||||
|
||||
|
||||
def decode_openrpc_json(json_string: str) -> OpenRPCSpec:
|
||||
d = json.loads(json_string)
|
||||
return OpenRPCSpec.load(d)
|
||||
|
||||
|
||||
def encode_openrpc_json(openrpc_object: OpenRPCSpec) -> str:
|
||||
# Convert the OpenRPCObject instance to a dictionary and then dump it to a JSON string
|
||||
return json.dumps(openrpc_object, indent=4)
|
||||
|
||||
|
||||
# check that the dict is well formatted
|
||||
def check(openrpc_spec: dict) -> bool:
|
||||
# todo, try to load the dict in openrpc object
|
||||
json_spec = json.dumps(openrpc_spec)
|
||||
try:
|
||||
decode_openrpc_json(json_spec)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from heroserver.openrpc.parser.cleaner import load
|
||||
from heroserver.openrpc.parser.parser import parser
|
||||
|
||||
openrpc_spec = parser(load("/root/code/git.threefold.info/projectmycelium/hero_server/lib/openrpclib/parser/examples"))
|
||||
|
||||
print(check(openrpc_spec))
|
329
_archive/openrpc/model/common.py
Normal file
329
_archive/openrpc/model/common.py
Normal file
@@ -0,0 +1,329 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
|
||||
class ReferenceObject:
|
||||
def __init__(self, ref: str = ""):
|
||||
self.ref = ref
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ReferenceObject":
|
||||
return cls(ref=data.get("$ref", ""))
|
||||
|
||||
|
||||
class SchemaObject:
|
||||
def __init__(
|
||||
self,
|
||||
title: Optional[str] = None,
|
||||
multipleOf: Optional[float] = None,
|
||||
maximum: Optional[float] = None,
|
||||
exclusiveMaximum: Optional[bool] = None,
|
||||
minimum: Optional[float] = None,
|
||||
exclusiveMinimum: Optional[bool] = None,
|
||||
maxLength: Optional[int] = None,
|
||||
minLength: Optional[int] = None,
|
||||
pattern: Optional[str] = None,
|
||||
maxItems: Optional[int] = None,
|
||||
minItems: Optional[int] = None,
|
||||
uniqueItems: Optional[bool] = None,
|
||||
maxProperties: Optional[int] = None,
|
||||
minProperties: Optional[int] = None,
|
||||
required: Optional[List[str]] = None,
|
||||
enum: Optional[List[Any]] = None,
|
||||
type: Optional[str] = None,
|
||||
allOf: Optional[List[Union["SchemaObject", ReferenceObject]]] = None,
|
||||
oneOf: Optional[List[Union["SchemaObject", ReferenceObject]]] = None,
|
||||
anyOf: Optional[List[Union["SchemaObject", ReferenceObject]]] = None,
|
||||
not_: Optional[Union["SchemaObject", ReferenceObject]] = None,
|
||||
items: Optional[
|
||||
Union[
|
||||
"SchemaObject",
|
||||
ReferenceObject,
|
||||
List[Union["SchemaObject", ReferenceObject]],
|
||||
]
|
||||
] = None,
|
||||
properties: Optional[Dict[str, Union["SchemaObject", ReferenceObject]]] = None,
|
||||
additionalProperties: Optional[Union[bool, "SchemaObject"]] = None,
|
||||
description: Optional[str] = None,
|
||||
format: Optional[str] = None,
|
||||
default: Optional[Any] = None,
|
||||
xtags: Optional[List[str]] = None,
|
||||
example: Optional[str] = None,
|
||||
):
|
||||
self.title = title
|
||||
self.multipleOf = multipleOf
|
||||
self.maximum = maximum
|
||||
self.exclusiveMaximum = exclusiveMaximum
|
||||
self.minimum = minimum
|
||||
self.exclusiveMinimum = exclusiveMinimum
|
||||
self.maxLength = maxLength
|
||||
self.minLength = minLength
|
||||
self.pattern = pattern
|
||||
self.maxItems = maxItems
|
||||
self.minItems = minItems
|
||||
self.uniqueItems = uniqueItems
|
||||
self.maxProperties = maxProperties
|
||||
self.minProperties = minProperties
|
||||
self.required = required
|
||||
self.enum = enum
|
||||
self.type = type
|
||||
self.allOf = allOf
|
||||
self.oneOf = oneOf
|
||||
self.anyOf = anyOf
|
||||
self.not_ = not_
|
||||
self.items = items
|
||||
self.properties = properties
|
||||
self.additionalProperties = additionalProperties
|
||||
self.description = description
|
||||
self.format = format
|
||||
self.default = default
|
||||
self.xtags = xtags
|
||||
self.example = example
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "SchemaObject":
|
||||
return cls(
|
||||
title=data.get("title"),
|
||||
multipleOf=data.get("multipleOf"),
|
||||
maximum=data.get("maximum"),
|
||||
exclusiveMaximum=data.get("exclusiveMaximum"),
|
||||
minimum=data.get("minimum"),
|
||||
exclusiveMinimum=data.get("exclusiveMinimum"),
|
||||
maxLength=data.get("maxLength"),
|
||||
minLength=data.get("minLength"),
|
||||
pattern=data.get("pattern"),
|
||||
maxItems=data.get("maxItems"),
|
||||
minItems=data.get("minItems"),
|
||||
uniqueItems=data.get("uniqueItems"),
|
||||
maxProperties=data.get("maxProperties"),
|
||||
minProperties=data.get("minProperties"),
|
||||
required=data.get("required"),
|
||||
enum=data.get("enum"),
|
||||
type=data.get("type"),
|
||||
allOf=(
|
||||
[
|
||||
(
|
||||
ReferenceObject.load(item)
|
||||
if "$ref" in item
|
||||
else SchemaObject.load(item)
|
||||
)
|
||||
for item in data.get("allOf", [])
|
||||
]
|
||||
if "allOf" in data
|
||||
else None
|
||||
),
|
||||
oneOf=(
|
||||
[
|
||||
(
|
||||
ReferenceObject.load(item)
|
||||
if "$ref" in item
|
||||
else SchemaObject.load(item)
|
||||
)
|
||||
for item in data.get("oneOf", [])
|
||||
]
|
||||
if "oneOf" in data
|
||||
else None
|
||||
),
|
||||
anyOf=(
|
||||
[
|
||||
(
|
||||
ReferenceObject.load(item)
|
||||
if "$ref" in item
|
||||
else SchemaObject.load(item)
|
||||
)
|
||||
for item in data.get("anyOf", [])
|
||||
]
|
||||
if "anyOf" in data
|
||||
else None
|
||||
),
|
||||
not_=(
|
||||
(
|
||||
ReferenceObject.load(data)
|
||||
if "$ref" in data
|
||||
else SchemaObject.load(data)
|
||||
)
|
||||
if "not" in data
|
||||
else None
|
||||
),
|
||||
items=(
|
||||
(
|
||||
ReferenceObject.load(data["items"])
|
||||
if "$ref" in data["items"]
|
||||
else SchemaObject.load(data["items"])
|
||||
)
|
||||
if isinstance(data.get("items"), dict)
|
||||
else (
|
||||
[
|
||||
(
|
||||
ReferenceObject.load(item)
|
||||
if "$ref" in item
|
||||
else SchemaObject.load(item)
|
||||
)
|
||||
for item in data.get("items", [])
|
||||
]
|
||||
if "items" in data
|
||||
else None
|
||||
)
|
||||
),
|
||||
properties=(
|
||||
{
|
||||
k: (
|
||||
ReferenceObject.load(v) if "$ref" in v else SchemaObject.load(v)
|
||||
)
|
||||
for k, v in data.get("properties", {}).items()
|
||||
}
|
||||
if "properties" in data
|
||||
else None
|
||||
),
|
||||
additionalProperties=(
|
||||
SchemaObject.load(data["additionalProperties"])
|
||||
if isinstance(data.get("additionalProperties"), dict)
|
||||
else data.get("additionalProperties")
|
||||
),
|
||||
description=data.get("description"),
|
||||
format=data.get("format"),
|
||||
default=data.get("default"),
|
||||
xtags=data.get("x-tags"),
|
||||
example=data.get("example"),
|
||||
)
|
||||
|
||||
|
||||
class ContentDescriptorObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
schema: Union[SchemaObject, ReferenceObject],
|
||||
summary: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
required: Optional[bool] = None,
|
||||
deprecated: Optional[bool] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.required = required
|
||||
self.schema = schema
|
||||
self.deprecated = deprecated
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ContentDescriptorObject":
|
||||
return cls(
|
||||
name=data["name"],
|
||||
summary=data.get("summary"),
|
||||
description=data.get("description"),
|
||||
required=data.get("required"),
|
||||
schema=(
|
||||
ReferenceObject.load(data["schema"])
|
||||
if "$ref" in data["schema"]
|
||||
else SchemaObject.load(data["schema"])
|
||||
),
|
||||
deprecated=data.get("deprecated"),
|
||||
)
|
||||
|
||||
|
||||
class ExternalDocumentationObject:
|
||||
def __init__(self, url: str, description: Optional[str] = None):
|
||||
self.description = description
|
||||
self.url = url
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ExternalDocumentationObject":
|
||||
return cls(description=data.get("description"), url=data["url"])
|
||||
|
||||
|
||||
class ExampleObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
summary: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
value: Optional[Any] = None,
|
||||
externalValue: Optional[str] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.value = value
|
||||
self.externalValue = externalValue
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ExampleObject":
|
||||
return cls(
|
||||
name=data["name"],
|
||||
summary=data.get("summary"),
|
||||
description=data.get("description"),
|
||||
value=data.get("value"),
|
||||
externalValue=data.get("externalValue"),
|
||||
)
|
||||
|
||||
|
||||
class ErrorObject:
|
||||
def __init__(self, code: int, message: str, data: Optional[Any] = None):
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.data = data
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ErrorObject":
|
||||
return cls(code=data["code"], message=data["message"], data=data.get("data"))
|
||||
|
||||
|
||||
class ExamplePairingObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
result: Union[ExampleObject, ReferenceObject],
|
||||
params: List[ExampleObject],
|
||||
description: Optional[str] = None,
|
||||
summary: Optional[str] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.summary = summary
|
||||
self.params = params
|
||||
self.result = result
|
||||
|
||||
def get_x() -> Union[str, int]:
|
||||
a = [1, 2, 3]
|
||||
b = ["a", "b", "c"]
|
||||
z = Union()
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ExamplePairingObject":
|
||||
return cls(
|
||||
name=data["name"],
|
||||
description=data.get("description"),
|
||||
summary=data.get("summary"),
|
||||
params=[ExampleObject.load(item) for item in data["params"]],
|
||||
result=(
|
||||
ExampleObject.load(data["result"])
|
||||
if isinstance(data["result"], dict) and "value" in data["result"]
|
||||
else ReferenceObject.load(data["result"])
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TagObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
summary: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
externalDocs: Optional[ExternalDocumentationObject] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.externalDocs = externalDocs
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "TagObject":
|
||||
return cls(
|
||||
name=data["name"],
|
||||
summary=data.get("summary"),
|
||||
description=data.get("description"),
|
||||
externalDocs=(
|
||||
ExternalDocumentationObject.load(data["externalDocs"])
|
||||
if "externalDocs" in data
|
||||
else None
|
||||
),
|
||||
)
|
44
_archive/openrpc/model/components.py
Normal file
44
_archive/openrpc/model/components.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
from heroserver.openrpc.model.common import (
|
||||
ContentDescriptorObject,
|
||||
ErrorObject,
|
||||
ExampleObject,
|
||||
ExamplePairingObject,
|
||||
ReferenceObject,
|
||||
SchemaObject,
|
||||
TagObject,
|
||||
)
|
||||
from heroserver.openrpc.model.server import LinkObject
|
||||
|
||||
|
||||
class ComponentsObject:
|
||||
def __init__(
|
||||
self,
|
||||
contentDescriptors: Dict[str, ContentDescriptorObject],
|
||||
schemas: Dict[str, Union[SchemaObject, ReferenceObject]],
|
||||
examples: Dict[str, ExampleObject],
|
||||
links: Dict[str, LinkObject],
|
||||
errors: Dict[str, ErrorObject],
|
||||
examplePairingObjects: Dict[str, ExamplePairingObject],
|
||||
tags: Dict[str, TagObject],
|
||||
):
|
||||
self.contentDescriptors = contentDescriptors
|
||||
self.schemas = schemas
|
||||
self.examples = examples
|
||||
self.links = links
|
||||
self.errors = errors
|
||||
self.examplePairingObjects = examplePairingObjects
|
||||
self.tags = tags
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ComponentsObject":
|
||||
return cls(
|
||||
contentDescriptors={k: ContentDescriptorObject.load(v) for k, v in data.get("contentDescriptors", {}).items()},
|
||||
schemas={k: ReferenceObject.load(v) if "$ref" in v else SchemaObject.load(v) for k, v in data.get("schemas", {}).items()},
|
||||
examples={k: ExampleObject.load(v) for k, v in data.get("examples", {}).items()},
|
||||
links={k: LinkObject.load(v) for k, v in data.get("links", {}).items()},
|
||||
errors={k: ErrorObject.load(v) for k, v in data.get("errors", {}).items()},
|
||||
examplePairingObjects={k: ExamplePairingObject.load(v) for k, v in data.get("examplePairingObjects", {}).items()},
|
||||
tags={k: TagObject.load(v) for k, v in data.get("tags", {}).items()},
|
||||
)
|
56
_archive/openrpc/model/info.py
Normal file
56
_archive/openrpc/model/info.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
class ContactObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
email: Optional[str] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.email = email
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ContactObject":
|
||||
return cls(name=data.get("name"), url=data.get("url"), email=data.get("email"))
|
||||
|
||||
|
||||
class LicenseObject:
|
||||
def __init__(self, name: str, url: Optional[str] = None):
|
||||
self.name = name
|
||||
self.url = url
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "LicenseObject":
|
||||
return cls(name=data["name"], url=data.get("url"))
|
||||
|
||||
|
||||
class InfoObject:
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
version: str,
|
||||
description: Optional[str] = None,
|
||||
termsOfService: Optional[str] = None,
|
||||
contact: Optional[ContactObject] = None,
|
||||
license: Optional[LicenseObject] = None,
|
||||
):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.termsOfService = termsOfService
|
||||
self.contact = contact
|
||||
self.license = license
|
||||
self.version = version
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "InfoObject":
|
||||
return cls(
|
||||
title=data["title"],
|
||||
description=data.get("description"),
|
||||
termsOfService=data.get("termsOfService"),
|
||||
contact=ContactObject.load(data["contact"]) if "contact" in data else None,
|
||||
license=LicenseObject.load(data["license"]) if "license" in data else None,
|
||||
version=data["version"],
|
||||
)
|
91
_archive/openrpc/model/methods.py
Normal file
91
_archive/openrpc/model/methods.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from heroserver.openrpc.model.common import (
|
||||
ContentDescriptorObject,
|
||||
ErrorObject,
|
||||
ExamplePairingObject,
|
||||
ExternalDocumentationObject,
|
||||
ReferenceObject,
|
||||
TagObject,
|
||||
)
|
||||
from heroserver.openrpc.model.server import LinkObject, ServerObject
|
||||
|
||||
|
||||
class MethodObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
params: List[Union[ContentDescriptorObject, ReferenceObject]],
|
||||
result: Union[ContentDescriptorObject, ReferenceObject, None],
|
||||
tags: Optional[List[Union[TagObject, ReferenceObject]]] = None,
|
||||
summary: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
externalDocs: Optional[ExternalDocumentationObject] = None,
|
||||
deprecated: Optional[bool] = None,
|
||||
servers: Optional[List[ServerObject]] = None,
|
||||
errors: Optional[List[Union[ErrorObject, ReferenceObject]]] = None,
|
||||
links: Optional[List[Union[LinkObject, ReferenceObject]]] = None,
|
||||
paramStructure: Optional[str] = None,
|
||||
examples: Optional[List[ExamplePairingObject]] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.tags = tags
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.externalDocs = externalDocs
|
||||
self.params = params
|
||||
self.result = result
|
||||
self.deprecated = deprecated
|
||||
self.servers = servers
|
||||
self.errors = errors
|
||||
self.links = links
|
||||
self.paramStructure = paramStructure
|
||||
self.examples = examples
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "MethodObject":
|
||||
return cls(
|
||||
name=data["name"],
|
||||
tags=(
|
||||
[
|
||||
(TagObject.load(item) if isinstance(item, dict) and "name" in item else ReferenceObject.load(item))
|
||||
for item in data.get("tags", [])
|
||||
]
|
||||
if "tags" in data
|
||||
else None
|
||||
),
|
||||
summary=data.get("summary"),
|
||||
description=data.get("description"),
|
||||
externalDocs=(ExternalDocumentationObject.load(data["externalDocs"]) if "externalDocs" in data else None),
|
||||
params=[
|
||||
(ContentDescriptorObject.load(item) if isinstance(item, dict) and "name" in item else ReferenceObject.load(item))
|
||||
for item in data["params"]
|
||||
],
|
||||
result=(
|
||||
ContentDescriptorObject.load(data["result"])
|
||||
if isinstance(data["result"], dict) and "name" in data["result"]
|
||||
else ReferenceObject.load(data["result"])
|
||||
if "result" in data
|
||||
else None
|
||||
),
|
||||
deprecated=data.get("deprecated"),
|
||||
servers=([ServerObject.load(item) for item in data.get("servers", [])] if "servers" in data else None),
|
||||
errors=(
|
||||
[
|
||||
(ErrorObject.load(item) if isinstance(item, dict) and "code" in item else ReferenceObject.load(item))
|
||||
for item in data.get("errors", [])
|
||||
]
|
||||
if "errors" in data
|
||||
else None
|
||||
),
|
||||
links=(
|
||||
[
|
||||
(LinkObject.load(item) if isinstance(item, dict) and "name" in item else ReferenceObject.load(item))
|
||||
for item in data.get("links", [])
|
||||
]
|
||||
if "links" in data
|
||||
else None
|
||||
),
|
||||
paramStructure=data.get("paramStructure"),
|
||||
examples=([ExamplePairingObject.load(item) for item in data.get("examples", [])] if "examples" in data else None),
|
||||
)
|
98
_archive/openrpc/model/openrpc_spec.py
Normal file
98
_archive/openrpc/model/openrpc_spec.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from heroserver.openrpc.model.common import (
|
||||
ContentDescriptorObject,
|
||||
ExternalDocumentationObject,
|
||||
ReferenceObject,
|
||||
SchemaObject,
|
||||
)
|
||||
from heroserver.openrpc.model.components import ComponentsObject
|
||||
from heroserver.openrpc.model.info import InfoObject
|
||||
from heroserver.openrpc.model.methods import MethodObject
|
||||
from heroserver.openrpc.model.server import ServerObject
|
||||
|
||||
ROOT_OBJ_DEF = "!!define.root_object"
|
||||
|
||||
|
||||
class OpenRPCSpec:
|
||||
def __init__(
|
||||
self,
|
||||
openrpc: str,
|
||||
info: InfoObject,
|
||||
methods: List[MethodObject],
|
||||
servers: Optional[List[ServerObject]] = None,
|
||||
components: Optional[ComponentsObject] = None,
|
||||
externalDocs: Optional[ExternalDocumentationObject] = None,
|
||||
spec_extensions: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
self.openrpc = openrpc
|
||||
self.info = info
|
||||
self.servers = servers
|
||||
self.methods = methods
|
||||
self.components = components
|
||||
self.externalDocs = externalDocs
|
||||
self.spec_extensions = spec_extensions
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "OpenRPCSpec":
|
||||
return cls(
|
||||
openrpc=data["openrpc"],
|
||||
info=InfoObject.load(data["info"]),
|
||||
servers=([ServerObject.load(item) for item in data.get("servers", [])] if "servers" in data else None),
|
||||
methods=[MethodObject.load(item) for item in data["methods"]],
|
||||
components=(ComponentsObject.load(data["components"]) if "components" in data else None),
|
||||
externalDocs=(ExternalDocumentationObject.load(data["externalDocs"]) if "externalDocs" in data else None),
|
||||
spec_extensions=data.get("spec_extensions"),
|
||||
)
|
||||
|
||||
def ref_to_schema(self, ref: str) -> Union[SchemaObject, ContentDescriptorObject]:
|
||||
if not ref.startswith("#/"):
|
||||
raise Exception(f"invalid ref: {ref}")
|
||||
|
||||
l = ref.split("/")[1:]
|
||||
obj = self
|
||||
for item in l:
|
||||
# TODO: find root cause of RO_
|
||||
if item.startswith("RO_"):
|
||||
item = item[3:]
|
||||
|
||||
if isinstance(obj, Dict):
|
||||
print("obj contents: ", obj)
|
||||
print("Trying to access key: ", item)
|
||||
obj = obj[item]
|
||||
else:
|
||||
obj = obj.__dict__[item]
|
||||
|
||||
if not isinstance(obj, SchemaObject) and not isinstance(obj, ContentDescriptorObject):
|
||||
raise Exception(f"ref to unsupported type: {ref}")
|
||||
|
||||
return obj
|
||||
|
||||
def get_root_objects(self) -> Dict[str, SchemaObject]:
|
||||
if not self.components:
|
||||
return {}
|
||||
|
||||
objs: Dict[str, SchemaObject] = {}
|
||||
base_ref = ["components", "schemas"]
|
||||
for name, scheme in self.components.schemas.items():
|
||||
if scheme.xtags and "rootobject" in scheme.xtags:
|
||||
objs["/".join(base_ref + [name.lower()])] = scheme
|
||||
|
||||
return objs
|
||||
|
||||
def set_root_objects(self, refs: List[str]):
|
||||
for ref in refs:
|
||||
obj = self.ref_to_schema(ref)
|
||||
if isinstance(obj, ContentDescriptorObject):
|
||||
obj = obj.schema
|
||||
if isinstance(obj, ReferenceObject):
|
||||
self.set_root_objects([obj.ref])
|
||||
continue
|
||||
|
||||
if not obj.description:
|
||||
obj.description = ROOT_OBJ_DEF
|
||||
else:
|
||||
obj.description += f";{ROOT_OBJ_DEF}"
|
||||
|
||||
|
||||
# Note that classes that refer to themselves or each other are handled using string literals in annotations to avoid forward reference issues. Python 3.7+ supports this feature with the use of 'from __future__ import annotations'.
|
84
_archive/openrpc/model/server.py
Normal file
84
_archive/openrpc/model/server.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
|
||||
class ServerVariableObject:
|
||||
def __init__(
|
||||
self,
|
||||
default: str,
|
||||
enum: Optional[List[str]] = None,
|
||||
description: Optional[str] = None,
|
||||
):
|
||||
self.enum = enum
|
||||
self.default = default
|
||||
self.description = description
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ServerVariableObject":
|
||||
return cls(
|
||||
enum=data.get("enum"),
|
||||
default=data["default"],
|
||||
description=data.get("description"),
|
||||
)
|
||||
|
||||
|
||||
class ServerObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
url: str,
|
||||
summary: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
variables: Optional[Dict[str, ServerVariableObject]] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.variables = variables
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "ServerObject":
|
||||
variables = (
|
||||
{
|
||||
k: ServerVariableObject.load(v)
|
||||
for k, v in data.get("variables", {}).items()
|
||||
}
|
||||
if "variables" in data
|
||||
else None
|
||||
)
|
||||
return cls(
|
||||
name=data["name"],
|
||||
url=data["url"],
|
||||
summary=data.get("summary"),
|
||||
description=data.get("description"),
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
|
||||
class LinkObject:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
method: str,
|
||||
params: Dict[str, Any],
|
||||
description: Optional[str] = None,
|
||||
summary: Optional[str] = None,
|
||||
server: Optional[ServerObject] = None,
|
||||
):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.summary = summary
|
||||
self.method = method
|
||||
self.params = params
|
||||
self.server = server
|
||||
|
||||
@classmethod
|
||||
def load(cls, data: Dict[str, Any]) -> "LinkObject":
|
||||
return cls(
|
||||
name=data["name"],
|
||||
description=data.get("description"),
|
||||
summary=data.get("summary"),
|
||||
method=data["method"],
|
||||
params=data["params"],
|
||||
server=ServerObject.load(data["server"]) if "server" in data else None,
|
||||
)
|
88
_archive/openrpc/parser/cleaner.py
Normal file
88
_archive/openrpc/parser/cleaner.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
|
||||
# remoces pub, mut, non needed code, ...
|
||||
def cleaner(code: str):
|
||||
lines = code.split("\n")
|
||||
processed_lines = []
|
||||
in_function = False
|
||||
in_struct_or_enum = False
|
||||
|
||||
for line in lines:
|
||||
line = line.replace("\t", " ")
|
||||
stripped_line = line.strip()
|
||||
|
||||
# Skip lines starting with 'pub mut:'
|
||||
if re.match(r"^\s*pub\s*(\s+mut\s*)?:", stripped_line):
|
||||
continue
|
||||
|
||||
# Remove 'pub ' at the start of struct and function lines
|
||||
if stripped_line.startswith("pub "):
|
||||
line = line.lstrip()[4:] # Remove leading spaces and 'pub '
|
||||
|
||||
# Check if we're entering or exiting a struct or enum
|
||||
if re.match(r"(struct|enum)\s+\w+\s*{", stripped_line):
|
||||
in_struct_or_enum = True
|
||||
processed_lines.append(line)
|
||||
elif in_struct_or_enum and "}" in stripped_line:
|
||||
in_struct_or_enum = False
|
||||
processed_lines.append(line)
|
||||
elif in_struct_or_enum:
|
||||
# Ensure consistent indentation within structs and enums
|
||||
processed_lines.append(line)
|
||||
else:
|
||||
# Handle function declarations
|
||||
if "fn " in stripped_line:
|
||||
if "{" in stripped_line:
|
||||
# Function declaration and opening brace on the same line
|
||||
in_function = True
|
||||
processed_lines.append(line)
|
||||
else:
|
||||
return Exception(f"accolade needs to be in fn line.\n{line}")
|
||||
elif in_function:
|
||||
if stripped_line == "}":
|
||||
# Closing brace of the function
|
||||
in_function = False
|
||||
processed_lines.append("}")
|
||||
# Skip all other lines inside the function
|
||||
else:
|
||||
processed_lines.append(line)
|
||||
|
||||
return "\n".join(processed_lines)
|
||||
|
||||
|
||||
def load(path: str) -> str:
|
||||
# walk over directory find all .v files, recursive
|
||||
# ignore all imports (import at start of line)
|
||||
# ignore all module ... (module at start of line)
|
||||
path = os.path.expanduser(path)
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"The path '{path}' does not exist.")
|
||||
all_code = []
|
||||
# Walk over directory recursively
|
||||
for root, _, files in os.walk(path):
|
||||
for file in files:
|
||||
if file.endswith(".v"):
|
||||
file_path = os.path.join(root, file)
|
||||
with open(file_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Filter out import and module lines
|
||||
filtered_lines = [
|
||||
line
|
||||
for line in lines
|
||||
if not line.strip().startswith(("import", "module"))
|
||||
]
|
||||
|
||||
all_code.append("".join(filtered_lines))
|
||||
|
||||
return "\n\n".join(all_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# from heroserver.openrpc.parser.example import load_example
|
||||
code = load("~/code/git.threefold.info/projectmycelium/hero_server/lib/openrpclib/parser/examples")
|
||||
# Parse the code
|
||||
code = cleaner(code)
|
||||
print(code)
|
92
_archive/openrpc/parser/cleaner.v
Normal file
92
_archive/openrpc/parser/cleaner.v
Normal file
@@ -0,0 +1,92 @@
|
||||
module main
|
||||
|
||||
import os
|
||||
import regex as re
|
||||
|
||||
// Removes pub, mut, unneeded code, etc.
|
||||
fn cleaner(code string) string {
|
||||
lines := code.split_into_lines()
|
||||
mut processed_lines := []string{}
|
||||
mut in_function := false
|
||||
mut in_struct_or_enum := false
|
||||
|
||||
for line in lines {
|
||||
line = line.replace('\t', ' ')
|
||||
stripped_line := line.trim_space()
|
||||
|
||||
// Skip lines starting with 'pub mut:'
|
||||
if stripped_line.starts_with('pub mut:') {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove 'pub ' at the start of struct and function lines
|
||||
if stripped_line.starts_with('pub ') {
|
||||
line = line.trim_left()[4..] // Remove leading spaces and 'pub '
|
||||
}
|
||||
|
||||
// Check if we're entering or exiting a struct or enum
|
||||
mut r := re.regex_opt(r'(struct|enum)\s+\w+\s*{') or { panic(err) }
|
||||
if r.matches_string(stripped_line) {
|
||||
in_struct_or_enum = true
|
||||
processed_lines << line
|
||||
} else if in_struct_or_enum && '}' in stripped_line {
|
||||
in_struct_or_enum = false
|
||||
processed_lines << line
|
||||
} else if in_struct_or_enum {
|
||||
// Ensure consistent indentation within structs and enums
|
||||
processed_lines << line
|
||||
} else {
|
||||
// Handle function declarations
|
||||
r = re.regex_opt(r'fn\s+\w+') or { panic(err) }
|
||||
if r.matches_string(stripped_line) {
|
||||
if '{' in stripped_line {
|
||||
// Function declaration and opening brace on the same line
|
||||
in_function = true
|
||||
processed_lines << line
|
||||
} else {
|
||||
return error('accolade needs to be in fn line.\n${line}')
|
||||
}
|
||||
} else if in_function {
|
||||
if stripped_line == '}' {
|
||||
// Closing brace of the function
|
||||
in_function = false
|
||||
processed_lines << '}'
|
||||
}
|
||||
// Skip all other lines inside the function
|
||||
} else {
|
||||
processed_lines << line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processed_lines.join('\n')
|
||||
}
|
||||
|
||||
fn load(path string) !string {
|
||||
// Walk over directory, find all .v files recursively.
|
||||
// Ignore all imports (import at start of line)
|
||||
// Ignore all module ... (module at start of line)
|
||||
path = os.expand_env(path)
|
||||
if !os.exists(path) {
|
||||
panic('The path "${path}" does not exist.')
|
||||
}
|
||||
// Walk over directory recursively
|
||||
os.walk_ext(path, '.v', fn (path string, _ []os.FileInfo) {
|
||||
t+=process_file(path)!
|
||||
}
|
||||
|
||||
fn process_file(file_path string) !string {
|
||||
lines := os.read_lines(file_path) or { return err }
|
||||
// Filter out import and module lines
|
||||
filtered_lines := lines.filter(it !in ['import', 'module'].map(it.trim_space()))
|
||||
|
||||
return filtered_lines.join('\n')
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// from heroserver.openrpc.parser.example import load_example
|
||||
code := load('~/code/git.threefold.info/hero/hero_server/lib/openrpclib/parser/examples')
|
||||
// Parse the code
|
||||
code = cleaner(code)!
|
||||
println(code)
|
||||
}
|
27
_archive/openrpc/parser/example.py
Normal file
27
_archive/openrpc/parser/example.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def load_example() -> str:
|
||||
# Start from the current working directory
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
examples_dir = os.path.join(current_dir, "examples")
|
||||
|
||||
examples = ""
|
||||
if os.path.isdir(examples_dir):
|
||||
examples = load_v_files(examples_dir)
|
||||
|
||||
return examples
|
||||
|
||||
|
||||
def load_v_files(path: str) -> str:
|
||||
examples = ""
|
||||
for entry in os.listdir(path):
|
||||
if os.path.isdir(entry):
|
||||
examples += load_v_files(entry) + "\n\n"
|
||||
elif entry.endswith(".v"):
|
||||
with open(entry, "r") as file:
|
||||
examples += file.read() + "\n"
|
||||
|
||||
return examples
|
107
_archive/openrpc/parser/includes.py
Normal file
107
_archive/openrpc/parser/includes.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import os
|
||||
|
||||
def includes_process_text(text):
|
||||
lines = text.split('\n')
|
||||
result = {}
|
||||
current_block = None
|
||||
current_content = []
|
||||
|
||||
for line in lines:
|
||||
stripped_line = line.strip()
|
||||
if stripped_line.startswith('<') and stripped_line.endswith('>') and not stripped_line.startswith('<END'):
|
||||
if current_block:
|
||||
raise Exception(f"should not come here, there needs to be <END> after a block.\n{line}")
|
||||
# result[current_block.upper()] = '\n'.join(current_content).rstrip()
|
||||
current_block = stripped_line[1:-1] # Remove '<' and '>'
|
||||
current_content = []
|
||||
elif stripped_line == '<END>':
|
||||
if current_block:
|
||||
result[current_block] = '\n'.join(current_content).rstrip()
|
||||
current_block = None
|
||||
current_content = []
|
||||
elif current_block is not None:
|
||||
current_content.append(line)
|
||||
|
||||
if current_block:
|
||||
raise Exception(f"should not come here, there needs to be <END> after a block.\n{line}")
|
||||
result[current_block] = '\n'.join(current_content).rstrip()
|
||||
|
||||
return result
|
||||
|
||||
def include_process_directory(path):
|
||||
path = os.path.expanduser(path)
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"The path '{path}' does not exist.")
|
||||
all_blocks = {}
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
if file.startswith('include_'):
|
||||
file_path = os.path.join(root, file)
|
||||
print(f" -- include {file_path}")
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
blocks = includes_process_text(content)
|
||||
all_blocks.update(blocks)
|
||||
return all_blocks
|
||||
|
||||
def include_process_text(input_text, block_dict):
|
||||
lines = input_text.split('\n')
|
||||
result_lines = []
|
||||
|
||||
for line in lines:
|
||||
stripped_line = line.strip()
|
||||
if stripped_line.startswith('//include<') and stripped_line.endswith('>'):
|
||||
key = stripped_line[10:-1].upper() # Extract and uppercase the key
|
||||
if key in block_dict:
|
||||
# Include the block exactly as it is in the dictionary
|
||||
result_lines.append(block_dict[key])
|
||||
else:
|
||||
result_lines.append(f"// ERROR: Block '{key}' not found in dictionary")
|
||||
else:
|
||||
result_lines.append(line)
|
||||
|
||||
return '\n'.join(result_lines)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage
|
||||
input_text = """
|
||||
<BASE>
|
||||
oid string //is unique id for user in a circle, example=a7c *
|
||||
name string //short name for swimlane'
|
||||
time_creation int //time when signature was created, in epoch example=1711442827 *
|
||||
comments []string //list of oid's of comments linked to this story
|
||||
<END>
|
||||
|
||||
<MYNAME>
|
||||
this is my name, one line only
|
||||
<END>
|
||||
"""
|
||||
|
||||
#parsed_blocks = include_parse_blocks(input_text)
|
||||
|
||||
includes_dict = include_process_directory("~/code/git.threefold.info/projectmycelium/hero_server/lib/openrpclib/parser/examples")
|
||||
|
||||
for key, value in includes_dict.items():
|
||||
print(f"{key}:")
|
||||
print(value)
|
||||
print() # Add a blank line between blocks for readability
|
||||
|
||||
input_text = '''
|
||||
//we didn't do anything for comments yet
|
||||
//
|
||||
//this needs to go to description in openrpc spec
|
||||
//
|
||||
@[rootobject]
|
||||
struct Story {
|
||||
//include<BASE>
|
||||
content string //description of the milestone example="this is example content which gives more color" *
|
||||
owners []string //list of users (oid) who are the owners of this project example="10a,g6,aa1" *
|
||||
notifications []string //list of users (oid) who want to be informed of changes of this milestone example="ad3"
|
||||
deadline int //epoch deadline for the milestone example="1711442827" *
|
||||
projects []string //link to a projects this story belongs too
|
||||
milestones []string //link to the mulestones this story belongs too
|
||||
}
|
||||
'''
|
||||
|
||||
result = include_process_text(input_text, includes_dict)
|
||||
print(result)
|
245
_archive/openrpc/parser/parser.py
Normal file
245
_archive/openrpc/parser/parser.py
Normal file
@@ -0,0 +1,245 @@
|
||||
import json
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
import yaml # type: ignore
|
||||
|
||||
from heroserver.openrpc.parser.cleaner import cleaner, load
|
||||
from heroserver.openrpc.parser.includes import include_process_directory, include_process_text, includes_process_text
|
||||
from heroserver.openrpc.parser.splitter import CodeType, splitter
|
||||
|
||||
# use https://regex101.com/
|
||||
|
||||
|
||||
def parse_field_description(field_description):
|
||||
# Initialize the result dictionary
|
||||
result = {"description": "", "index": False, "example": None}
|
||||
|
||||
# Check if the field is indexed
|
||||
if field_description.strip().endswith("*"):
|
||||
result["index"] = True
|
||||
field_description = field_description.strip()[:-1].strip()
|
||||
|
||||
# Split the description and example
|
||||
parts = field_description.split("example=", 1)
|
||||
|
||||
# Set the description
|
||||
result["description"] = parts[0].strip()
|
||||
|
||||
# Extract the example if it exists
|
||||
if len(parts) > 1:
|
||||
example_value = parts[1].strip()
|
||||
if example_value.startswith("[") and example_value.endswith("]"):
|
||||
result["example"] = json.loads(example_value)
|
||||
elif example_value.isdigit():
|
||||
result["example"] = int(example_value)
|
||||
else:
|
||||
example_match = re.search(r'["\'](.+?)["\']', example_value)
|
||||
if example_match:
|
||||
result["example"] = example_match.group(1)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_struct(struct_def):
|
||||
struct_name = re.search(r"struct (\w+)", struct_def).group(1)
|
||||
fields = re.findall(r"\s+(\w+)\s+([\w\[\]]+)(?:\s*\/\/(.+))?", struct_def)
|
||||
return struct_name, fields
|
||||
|
||||
|
||||
def parse_enum(enum_def):
|
||||
enum_name = re.search(r"enum (\w+)", enum_def).group(1)
|
||||
values = re.findall(r"\n\s+(\w+)", enum_def)
|
||||
return enum_name, values
|
||||
|
||||
|
||||
def parse_function(func_def):
|
||||
# Match the function signature
|
||||
match = re.search(r"fn (\w+)\((.*?)\)\s*(!?\w*)", func_def)
|
||||
if match:
|
||||
func_name = match.group(1)
|
||||
params_str = match.group(2).strip()
|
||||
return_type = match.group(3).strip()
|
||||
|
||||
if return_type.startswith("RO_"):
|
||||
return_type = return_type[3:]
|
||||
if return_type.startswith("!RO_"):
|
||||
return_type = return_type[4:]
|
||||
if return_type.startswith("?RO_"):
|
||||
return_type = return_type[4:]
|
||||
|
||||
# print(f" -- return type: {return_type}")
|
||||
|
||||
# Parse parameters
|
||||
params = []
|
||||
if params_str:
|
||||
# This regex handles parameters with or without type annotations
|
||||
param_pattern = re.compile(r"(\w+)(?:\s+(\w+))?")
|
||||
for param_match in param_pattern.finditer(params_str):
|
||||
param_name, param_type = param_match.groups()
|
||||
if param_type.startswith("RO_"):
|
||||
param_type = param_type[3:]
|
||||
params.append((param_name, param_type if param_type else None))
|
||||
|
||||
return func_name, params, return_type
|
||||
return None, None, None
|
||||
|
||||
|
||||
def get_type_schema(type_name):
|
||||
if type_name.startswith("[]"):
|
||||
item_type = type_name[2:]
|
||||
return {"type": "array", "items": get_type_schema(item_type)}
|
||||
elif type_name in ["string"]:
|
||||
return {"type": "string"}
|
||||
elif type_name in ["f64", "float", "f32", "f16"]:
|
||||
return {"type": "number"}
|
||||
elif type_name in ["int"]:
|
||||
return {"type": "integer"}
|
||||
elif type_name == "bool":
|
||||
return {"type": "boolean"}
|
||||
elif type_name == "":
|
||||
return {"type": "null"}
|
||||
else:
|
||||
return {"$ref": f"#/components/schemas/{type_name}"}
|
||||
|
||||
|
||||
def parser(code: str = "", path: str = "") -> dict:
|
||||
if len(code) > 0 and len(path) > 0:
|
||||
raise Exception("cannot have code and path filled in at same time")
|
||||
if len(path) > 0:
|
||||
code = load(path)
|
||||
includes_dict = include_process_directory(path)
|
||||
else:
|
||||
includes_dict = includes_process_text(path)
|
||||
|
||||
openrpc_spec = {
|
||||
"openrpc": "1.2.6",
|
||||
"info": {"title": "V Code API", "version": "1.0.0"},
|
||||
"methods": [],
|
||||
"components": {"schemas": {}},
|
||||
}
|
||||
|
||||
# this function just cleans the code so we have a proper input for the parser
|
||||
code = cleaner(code)
|
||||
|
||||
# this function is a pre-processor, it finds include blocks and adds them in
|
||||
code = include_process_text(code, includes_dict)
|
||||
|
||||
codeblocks = splitter(code)
|
||||
|
||||
structs: List[Tuple[dict, List[str]]] = list()
|
||||
enums = list()
|
||||
functions = list()
|
||||
|
||||
for item in codeblocks:
|
||||
if item["type"] == CodeType.STRUCT:
|
||||
structs.append((item["block"], item["comments"]))
|
||||
if item["type"] == CodeType.ENUM:
|
||||
enums.append((item["block"], item["comments"]))
|
||||
if item["type"] == CodeType.FUNCTION:
|
||||
functions.append((item["block"], item["comments"]))
|
||||
|
||||
# Process structs and enums
|
||||
for item in structs:
|
||||
struct_name, fields = parse_struct(item[0])
|
||||
rootobject = False
|
||||
if struct_name.startswith("RO_"):
|
||||
rootobject = True
|
||||
struct_name = struct_name[3:]
|
||||
|
||||
openrpc_spec["components"]["schemas"][struct_name] = {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
}
|
||||
|
||||
for field in fields:
|
||||
field_name, field_type, field_description = field
|
||||
parsed_description = parse_field_description(field_description)
|
||||
|
||||
field_schema = {
|
||||
**get_type_schema(field_type),
|
||||
"description": parsed_description["description"],
|
||||
}
|
||||
|
||||
if parsed_description["example"]:
|
||||
field_schema["example"] = parsed_description["example"]
|
||||
|
||||
if parsed_description["index"]:
|
||||
field_schema["x-tags"] = field_schema.get("x-tags", []) + ["indexed"]
|
||||
|
||||
openrpc_spec["components"]["schemas"][struct_name]["properties"][field_name] = field_schema
|
||||
|
||||
if rootobject:
|
||||
openrpc_spec["components"]["schemas"][struct_name]["x-tags"] = ["rootobject"]
|
||||
|
||||
functions.append((f"fn {struct_name.lower()}_get(id string) {struct_name}", []))
|
||||
functions.append((f"fn {struct_name.lower()}_set(obj {struct_name})", []))
|
||||
functions.append((f"fn {struct_name.lower()}_delete(id string)", []))
|
||||
|
||||
for item in enums:
|
||||
enum_name, values = parse_enum(item[0])
|
||||
openrpc_spec["components"]["schemas"][enum_name] = {
|
||||
"type": "string",
|
||||
"enum": values,
|
||||
}
|
||||
|
||||
# print(functions)
|
||||
# from IPython import embed; embed()
|
||||
# Process functions
|
||||
for item in functions:
|
||||
func_name, params, return_type = parse_function(item[0])
|
||||
print(f"debugzooo {func_name} {params}")
|
||||
if return_type:
|
||||
return_type = return_type.lstrip("!")
|
||||
else:
|
||||
return_type = ""
|
||||
|
||||
if func_name:
|
||||
descr_return = f"Result of the {func_name} function is {return_type}"
|
||||
descr_function = f"Executes the {func_name} function"
|
||||
if len(item[1]) > 0:
|
||||
if isinstance(item[1], list):
|
||||
descr_function = "\n".join(item[1])
|
||||
else:
|
||||
descr_function = "\n".join(str(element) for element in item[1:])
|
||||
method = {
|
||||
"name": func_name,
|
||||
"description": descr_function,
|
||||
"params": [],
|
||||
"result": {
|
||||
"name": "result",
|
||||
"description": descr_return,
|
||||
"schema": get_type_schema(return_type),
|
||||
},
|
||||
}
|
||||
for param in params:
|
||||
# from IPython import embed; embed()
|
||||
if len(param) == 2:
|
||||
param_name, param_type = param
|
||||
method["params"].append(
|
||||
{
|
||||
"name": param_name,
|
||||
"description": f"Parameter {param_name} of type {param_type}",
|
||||
"schema": get_type_schema(param_type),
|
||||
}
|
||||
)
|
||||
openrpc_spec["methods"].append(method) # do it in the openrpc model
|
||||
|
||||
return openrpc_spec
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
openrpc_spec = parser(path="~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs")
|
||||
out = json.dumps(openrpc_spec, indent=2)
|
||||
# print(out)
|
||||
|
||||
filename = "/tmp/openrpc_spec.json"
|
||||
# Write the spec to the file
|
||||
with open(filename, "w") as f:
|
||||
f.write(out)
|
||||
print(f"OpenRPC specification (JSON) has been written to: {filename}")
|
||||
|
||||
yaml_filename = "/tmp/openrpc_spec.yaml"
|
||||
with open(yaml_filename, "w") as f:
|
||||
yaml.dump(openrpc_spec, f, sort_keys=False)
|
||||
print(f"OpenRPC specification (YAML) has been written to: {yaml_filename}")
|
80
_archive/openrpc/parser/splitter.py
Normal file
80
_archive/openrpc/parser/splitter.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from enum import Enum
|
||||
|
||||
from heroserver.openrpc.parser.cleaner import cleaner
|
||||
|
||||
|
||||
class CodeType(Enum):
|
||||
STRUCT = "struct"
|
||||
ENUM = "enum"
|
||||
FUNCTION = "function"
|
||||
|
||||
|
||||
def splitter(code: str):
|
||||
lines = code.split("\n")
|
||||
result = []
|
||||
current_block = None
|
||||
current_comments = []
|
||||
|
||||
for line in lines:
|
||||
line = line.replace("\t", " ")
|
||||
stripped_line = line.strip()
|
||||
|
||||
if stripped_line.startswith("//"):
|
||||
current_comments.append(stripped_line[2:].strip())
|
||||
elif stripped_line.startswith("struct "):
|
||||
if current_block:
|
||||
result.append(current_block)
|
||||
current_block = {
|
||||
"type": CodeType.STRUCT,
|
||||
"comments": current_comments,
|
||||
"block": line,
|
||||
}
|
||||
current_comments = []
|
||||
elif stripped_line.startswith("enum "):
|
||||
if current_block:
|
||||
result.append(current_block)
|
||||
current_block = {
|
||||
"type": CodeType.ENUM,
|
||||
"comments": current_comments,
|
||||
"block": line,
|
||||
}
|
||||
current_comments = []
|
||||
elif stripped_line.startswith("fn "):
|
||||
if current_block:
|
||||
result.append(current_block)
|
||||
current_block = {
|
||||
"type": CodeType.FUNCTION,
|
||||
"comments": current_comments,
|
||||
"block": line.split("{")[0].strip(),
|
||||
}
|
||||
current_comments = []
|
||||
elif current_block:
|
||||
if current_block["type"] == CodeType.STRUCT and stripped_line == "}":
|
||||
current_block["block"] += "\n" + line
|
||||
result.append(current_block)
|
||||
current_block = None
|
||||
elif current_block["type"] == CodeType.ENUM and stripped_line == "}":
|
||||
current_block["block"] += "\n" + line
|
||||
result.append(current_block)
|
||||
current_block = None
|
||||
elif current_block["type"] in [CodeType.STRUCT, CodeType.ENUM]:
|
||||
current_block["block"] += "\n" + line
|
||||
|
||||
if current_block:
|
||||
result.append(current_block)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from heroserver.openrpc.parser.cleaner import load
|
||||
|
||||
code = load("/root/code/git.threefold.info/projectmycelium/hero_server/lib/openrpclib/parser/examples")
|
||||
code = cleaner(code)
|
||||
# Test the function
|
||||
parsed_code = splitter(code)
|
||||
for item in parsed_code:
|
||||
print(f"Type: {item['type']}")
|
||||
print(f"Comments: {item['comments']}")
|
||||
print(f"Block:\n{item['block']}")
|
||||
print("-" * 50)
|
19
_archive/openrpc/readme.md
Normal file
19
_archive/openrpc/readme.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## example how to use
|
||||
|
||||
```python
|
||||
|
||||
from openrpc import openrpc_spec_write
|
||||
|
||||
#load all the specs and write the result in a dir
|
||||
openrpc_spec = openrpc_spec_write(
|
||||
path="~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs"
|
||||
dest="/tmp/openrpc/example1"
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
## internal process
|
||||
|
||||
- first we clean the code to only have relevant parts
|
||||
- then we find the blocks, can be function, enum or struct
|
||||
- then we parse the blocks
|
3
_archive/openrpc/tools/__init__.py
Normal file
3
_archive/openrpc/tools/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .tools_py import create_example_object, get_pydantic_type, get_return_type, topological_sort
|
||||
|
||||
__all__ = ["get_pydantic_type", "get_return_type", "topological_sort", "create_example_object"]
|
0
_archive/openrpc/tools/py.typed
Normal file
0
_archive/openrpc/tools/py.typed
Normal file
6
_archive/openrpc/tools/test_imports.py
Normal file
6
_archive/openrpc/tools/test_imports.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from heroserver.openrpc.tools import get_pydantic_type
|
||||
|
||||
# Simple test schema
|
||||
test_schema = {"type": "string", "format": "email"}
|
||||
result = get_pydantic_type(test_schema)
|
||||
print(f"Test passed: {result == 'Email'}")
|
128
_archive/openrpc/tools/tools_py.py
Normal file
128
_archive/openrpc/tools/tools_py.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from inspect import isclass
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
def get_pydantic_type(schema: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Convert OpenRPC schema types to Pydantic types.
|
||||
|
||||
Args:
|
||||
schema: OpenRPC schema dictionary
|
||||
|
||||
Returns:
|
||||
String representation of the Pydantic type
|
||||
"""
|
||||
if "type" in schema:
|
||||
if schema["type"] == "string":
|
||||
if "format" in schema and schema["format"] == "email":
|
||||
return "Email"
|
||||
return "str"
|
||||
elif schema["type"] == "integer":
|
||||
return "int"
|
||||
elif schema["type"] == "array":
|
||||
items_type = get_pydantic_type(schema["items"])
|
||||
return f"List[{items_type}]"
|
||||
elif schema["type"] == "object":
|
||||
return "dict"
|
||||
elif schema["type"] == "boolean":
|
||||
return "bool"
|
||||
elif schema["type"] == "null":
|
||||
return "None"
|
||||
elif "$ref" in schema:
|
||||
ref_name = schema["$ref"].split("/")[-1]
|
||||
return ref_name
|
||||
elif "anyOf" in schema:
|
||||
types = [get_pydantic_type(sub_schema) for sub_schema in schema["anyOf"]]
|
||||
if "None" in types:
|
||||
# Remove 'None' from the types list
|
||||
types = [t for t in types if t != "None"]
|
||||
if len(types) == 1:
|
||||
return f"Optional[{types[0]}]"
|
||||
else:
|
||||
return f"Optional[Union[{', '.join(types)}]]"
|
||||
else:
|
||||
return f"Union[{', '.join(types)}]"
|
||||
|
||||
return "Any"
|
||||
|
||||
|
||||
def get_return_type(method_result: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Get the return type from a method result schema.
|
||||
|
||||
Args:
|
||||
method_result: Method result dictionary containing schema or $ref
|
||||
|
||||
Returns:
|
||||
String representation of the return type
|
||||
"""
|
||||
if "schema" in method_result:
|
||||
schema = method_result["schema"]
|
||||
if "type" in schema:
|
||||
return get_pydantic_type(schema)
|
||||
elif "$ref" in schema:
|
||||
ref_name = schema["$ref"].split("/")[-1]
|
||||
return ref_name
|
||||
elif "anyOf" in schema:
|
||||
schema_list = schema["anyOf"]
|
||||
if isinstance(schema_list, list):
|
||||
return " | ".join(get_pydantic_type(sub_schema) for sub_schema in schema_list)
|
||||
return "Any"
|
||||
elif "$ref" in method_result: # Handle $ref at the top level
|
||||
ref_path = method_result["$ref"]
|
||||
if isinstance(ref_path, str):
|
||||
return ref_path.split("/")[-1]
|
||||
return ""
|
||||
|
||||
|
||||
def topological_sort(schema_dict: Dict[str, Any]) -> List[str]:
|
||||
visited = set()
|
||||
stack = []
|
||||
sorted_classes = []
|
||||
|
||||
def dfs(class_name: str) -> None:
|
||||
visited.add(class_name)
|
||||
if class_name in schema_dict:
|
||||
for prop in schema_dict[class_name].get("properties", {}).values():
|
||||
if "$ref" in prop:
|
||||
ref_name = prop["$ref"].split("/")[-1]
|
||||
if ref_name not in visited:
|
||||
dfs(ref_name)
|
||||
stack.append(class_name)
|
||||
|
||||
for class_name in schema_dict:
|
||||
if class_name not in visited:
|
||||
dfs(class_name)
|
||||
|
||||
while stack:
|
||||
sorted_classes.append(stack.pop())
|
||||
|
||||
return sorted_classes
|
||||
|
||||
|
||||
def create_example_object(cls: type[BaseModel]) -> BaseModel:
|
||||
"""
|
||||
Create an example object from a Pydantic model class using field examples.
|
||||
|
||||
Args:
|
||||
cls: A Pydantic BaseModel class
|
||||
|
||||
Returns:
|
||||
An instance of the provided model class with example data
|
||||
|
||||
Raises:
|
||||
ValueError: If cls is not a valid Pydantic BaseModel class
|
||||
"""
|
||||
if not isclass(cls) or not issubclass(cls, BaseModel):
|
||||
raise ValueError(f"{cls} is not a valid pydantic BaseModel class.")
|
||||
|
||||
example_data = {}
|
||||
|
||||
for field_name, field_info in cls.model_fields.items():
|
||||
examples = field_info.examples
|
||||
if examples:
|
||||
example_data[field_name] = examples[0]
|
||||
|
||||
return cls(**example_data)
|
Reference in New Issue
Block a user