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

View 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
),
)

View 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()},
)

View 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"],
)

View 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),
)

View 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'.

View 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,
)