This commit is contained in:
2025-08-05 15:15:36 +02:00
parent 4bd960ed05
commit 7fabb4163a
192 changed files with 14901 additions and 0 deletions

View File

@@ -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()

View File

@@ -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 %}

View File

@@ -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)

View File

@@ -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 %}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
import uvicorn
from open_api import app
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)