1
Fork 0
mirror of https://github.com/RGBCube/GitHubWrapper synced 2025-05-18 06:55:09 +00:00
GitHubWrapper/tools/unschema/parser.py
2022-06-26 17:28:01 +03:00

184 lines
6.4 KiB
Python

from __future__ import annotations
from typing import Tuple # , Any
types = {
"string": "str",
"number": "float",
"integer": "int",
"boolean": "bool",
"object": "dict",
"array": "list",
"null": "None",
}
# # Used for debugging
# class AppendPrint(list):
# def append(self, obj: Any) -> None:
# if obj:
# print(obj)
# super().append(obj)
def generate(
obj: dict, /, *, title: str = "GeneratedObject", no_comments: bool = False
) -> Tuple[str, str]: # sourcery skip: low-code-quality
"""Makes TypedDict from a JSON Schema object.
Arguments:
obj: JSON Schema dict.
title: The title of the result.
no_comments: If True, no comments will be added.
Returns:
The generated TypedDicts, the result values name and the comments.
"""
# The other TypedDicts
result = []
# The real annotation
annotation: str
obj_type = obj.get("type")
# allOf, anyOf, oneOf, (not)?
if not obj_type:
# Treating oneOf as allOf, since is kinda the
# same and there isn't a way to type it in Python
if objs := obj.get("anyOf") or obj.get("oneOf"):
union_items = []
optional = False
for obj_schema in objs:
if obj_schema["type"] == "null":
optional = True
else:
extras, target = generate(obj_schema, title=title, no_comments=no_comments)
result.append(extras)
union_items.append(target)
annotation = f"{'Optional' if optional else 'Union'}[{', '.join(union_items)}]"
else:
annotation = "Any"
# Union for parameters
elif isinstance(obj_type, list):
union_items = []
is_optional = False
for obj_type_item in obj_type:
if obj_type_item == "null":
is_optional = True
else:
union_items.append(types[obj_type_item])
annotation = f"{'Optional' if is_optional else 'Optional'}[{', '.join(union_items)}]"
elif obj_type == "boolean":
annotation = "bool"
elif obj_type == "null":
annotation = "None"
elif obj_type == "string":
if not no_comments:
if obj_min_len := obj.get("minLength"):
result.append(f" # Minimum length: {obj_min_len}")
if obj_max_len := obj.get("maxLength"):
result.append(f" # Maximum length: {obj_max_len}")
if obj_pattern := obj.get("pattern"):
result.append(f" # Pattern: {obj_pattern!r}")
annotation = "str"
elif obj_type in {"integer", "number"}:
if not no_comments:
if obj_multiple := obj.get("multipleOf"):
result.append(f" # Multiple of: {obj_multiple}")
if obj_minimum := obj.get("minimum"):
result.append(f" # Minimum (x >= N): {obj_minimum}")
if obj_exclusive_minimum := obj.get("exclusiveMinimum"):
result.append(f" # Exclusive minimum (x > N): {obj_exclusive_minimum}")
if obj_maximum := obj.get("maximum"):
result.append(f" # Maximum (x <= N): {obj_maximum}")
if obj_exclusive_maximum := obj.get("exclusiveMaximum"):
result.append(f" # Exclusive maximum (x < N): {obj_exclusive_maximum}")
if any([obj_minimum, obj_exclusive_minimum, obj_maximum, obj_exclusive_maximum]):
result.append(" # x = the variable, N = the min/max")
annotation = "int" if obj_type == "integer" else "float"
elif obj_type == "object":
# TODO: add support for patternProperties, unevaluatedProperties,
# propertyNames, minProperties, maxProperties
if obj_properties := obj.get("properties"):
# TODO: make it so the extra properties are typed instead of Any, somehow
total = ", total=False" if obj.get("additionalProperties") else ""
annotation = obj_title = obj.get("title", title).replace(" ", "")
typed_dict = [f"class {obj_title}(TypedDict{total}):"]
for key, value in obj_properties.items():
extras, param_annotation = generate(value, title=key.capitalize(), no_comments=no_comments)
result.append(extras)
if key not in obj.get("required", []):
param_annotation = f"NotRequired[{param_annotation}]"
if not no_comments:
typed_dict.extend(
[
f" # Format: {fmt}" if (fmt := value.get("format")) else "",
f" # Example: {', '.join(str(ex) for ex in exs)}"
if (exs := value.get("examples"))
else "",
]
)
typed_dict.append(f" {key}: {param_annotation}")
result.extend(typed_dict)
else:
annotation = "dict"
elif obj_type == "array":
# TODO: add support for contains, minContains,
# maxContains, minLength, maxLength, uniqueItems
if obj_items := obj.get("items"):
extras, target = generate(obj_items, no_comments=no_comments)
result.append(extras)
annotation = f"List[{target}]"
elif obj_prefix_items := obj.get("prefixItems"):
tuple_annotation = []
for item in obj_prefix_items:
(
extras,
target,
) = generate(item, no_comments=no_comments)
result.append(extras)
tuple_annotation.append(target)
if extra_item_type := obj.get("items"):
if extra_item_type is not True:
extras, extra_type = generate(extra_item_type, no_comments=no_comments)
result.append(extras)
if not no_comments:
result.append(
f" # The extra items for the tuple are typed as: {extra_type}"
)
tuple_annotation.append("...")
annotation = f"Tuple[{', '.join(tuple_annotation)}]"
else:
annotation = "list"
else:
annotation = "Any"
result = [i for i in result if i]
return "\n".join(result), annotation