diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..9eedfb8 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,107 @@ + + +
+ + +
+import inspect
+from collections.abc import Callable
+from types import NoneType, UnionType
+from typing import Any, get_args, get_origin
+
+from attrs import frozen
+from docstring_parser import parse as parse_docstring
+
+from .normalize import normalize_annotation
+from .schema import (
+ CallableSchema,
+ MappingNode,
+ ObjectField,
+ ObjectNode,
+ PrimitiveNode,
+ SchemaNode,
+ SequenceNode,
+ TupleNode,
+ UndefinedNode,
+ UnionNode,
+)
+
+__all__ = [
+ "annotation_to_schema",
+ "extract_schema",
+]
+
+
+@frozen
+class DocstringHints:
+ object_hint: str | None
+ param_to_hint: dict[str, str]
+
+
+def extract_hints(callable: Callable) -> DocstringHints:
+ docstring = inspect.getdoc(callable)
+ if docstring is not None:
+ docstring = parse_docstring(docstring)
+ object_hint = docstring.short_description
+ param_to_hint = {
+ param.arg_name: param.description
+ for param in docstring.params
+ if param.description
+ }
+ else:
+ object_hint = None
+ param_to_hint = {}
+
+ if isinstance(callable, type) and hasattr(callable, "__init__"):
+ init_hints = extract_hints(callable.__init__)
+ object_hint = object_hint or init_hints.object_hint
+ param_to_hint.update(init_hints.param_to_hint)
+
+ return DocstringHints(object_hint, param_to_hint)
+
+
+
+[docs]
+def annotation_to_schema(annotation) -> SchemaNode:
+ r"""Extract schema from the type annotation."""
+ if annotation in [Any, None]:
+ return UndefinedNode(original_annotation=annotation)
+
+ annotation = normalize_annotation(annotation, concretize=True)
+
+ if annotation in [int, float, str, bool]:
+ return PrimitiveNode(primitive_type=annotation) # type: ignore
+
+ origin = get_origin(annotation)
+ args = get_args(annotation)
+
+ if origin is None:
+ # Object node, so we need to extract fields from the constructor signature.
+ init_schema = extract_schema(annotation)
+ return init_schema.call_schema
+
+ # Normalization step has already converted all abstract classes to the corresponding concrete types.
+ # So we can safely check against list and dict.
+ if issubclass(origin, list):
+ assert (
+ len(args) == 1
+ ), "Sequence type annotation should have exactly one argument."
+ return SequenceNode(
+ sequence_type=origin,
+ item_schema=annotation_to_schema(args[0]),
+ )
+ if issubclass(origin, tuple):
+ return TupleNode(
+ tuple_type=origin,
+ item_schemas=[annotation_to_schema(arg) for arg in args],
+ )
+ if issubclass(origin, dict):
+ assert (
+ len(args) == 2
+ ), "Mapping type annotation should have exactly two arguments."
+ return MappingNode(
+ mapping_type=origin,
+ key_schema=annotation_to_schema(args[0]),
+ value_schema=annotation_to_schema(args[1]),
+ )
+ if issubclass(origin, UnionType):
+ arg_schemas = []
+ for arg in args:
+ if arg is NoneType:
+ arg_schemas.append(None)
+ else:
+ arg_schemas.append(annotation_to_schema(arg))
+ return UnionNode(options=arg_schemas)
+
+ raise ValueError(f"Unsupported type annotation: {annotation}")
+
+
+
+
+[docs]
+def extract_schema(callable: Callable) -> CallableSchema:
+ r"""Extract schema from a callable."""
+ signature = inspect.signature(callable, eval_str=True)
+ return_type = signature.return_annotation
+
+ hints = extract_hints(callable)
+
+ if return_type is inspect.Signature.empty:
+ return_schema = UndefinedNode(original_annotation=Any)
+ else:
+ return_schema = annotation_to_schema(return_type)
+
+ object_fields = []
+ for param in signature.parameters.values():
+ if param.annotation is inspect.Signature.empty:
+ arg_schema = UndefinedNode(original_annotation=Any)
+ else:
+ arg_schema = annotation_to_schema(param.annotation)
+ field = ObjectField(
+ param.name,
+ arg_schema,
+ hints.param_to_hint.get(param.name),
+ param.default is param.empty,
+ )
+ object_fields.append(field)
+
+ return CallableSchema(
+ call_schema=ObjectNode(
+ name=callable.__name__,
+ fields=object_fields,
+ hint=hints.object_hint,
+ ),
+ return_schema=return_schema,
+ )
+
+
+from collections.abc import Mapping, Sequence
+from types import NoneType
+from typing import Any
+
+from attrs import frozen
+
+__all__ = [
+ "CallableSchema",
+ "MappingNode",
+ "ObjectField",
+ "ObjectNode",
+ "PrimitiveNode",
+ "SchemaNode",
+ "SequenceNode",
+ "TupleNode",
+ "UndefinedNode",
+ "UnionNode",
+]
+
+
+
+
+
+
+
+[docs]
+@frozen
+class UndefinedNode(SchemaNode):
+ r"""Node corresponding to missing, `Any`, or `None` annotations.
+
+ Atributes
+ original_annotation: `Any` if the annotation is missing or `Any`,
+ `None` if the annotation is `None`.
+ """
+
+ original_annotation: NoneType | Any
+
+
+
+
+[docs]
+@frozen
+class PrimitiveNode(SchemaNode):
+ r"""Node corresponding to primitive types.
+
+ Primitime types are `int`, `float`, `str`, and `bool`.
+
+ Attributes:
+ primitive_type: The primitive type.
+ """
+
+ primitive_type: type[int] | type[float] | type[str] | type[bool]
+
+
+
+
+[docs]
+@frozen
+class SequenceNode(SchemaNode):
+ r"""Node corresponding to generic homogeneous sequence types.
+ (Does not include tuples and strings)
+
+ Attributes:
+ sequence_type: The sequence type.
+ item_schema: The schema of the items in the sequence.
+ """
+
+ sequence_type: type[Sequence]
+ item_schema: SchemaNode
+
+
+
+
+[docs]
+@frozen
+class TupleNode(SchemaNode):
+ r"""Node corresponding to tuples and named tuples.
+
+ Attributes:
+ tuple_type: The tuple type (`tuple`, or a subclass of `typing.NamedTuple`).
+ item_schemas: The schemas of the items in the tuple.
+ """
+
+ tuple_type: type[tuple]
+ item_schemas: list[SchemaNode]
+
+
+
+
+[docs]
+@frozen
+class MappingNode(SchemaNode):
+ r"""Node corresponding to generic mapping types.
+
+ Attributes:
+ mapping_type: The mapping type (e.g. `dict` or `collections.defaultdict`).
+ key_schema: The schema of the keys in the mapping.
+ value_schema: The schema of the values in the mapping.
+ """
+
+ mapping_type: type[Mapping]
+ key_schema: SchemaNode
+ value_schema: SchemaNode
+
+
+
+
+[docs]
+@frozen
+class UnionNode(SchemaNode):
+ r"""Node corresponding to `Union` and `Optional` types.
+
+ Attributes:
+ options: The schemas of the options in the union.
+ May be None in case of optional types.
+ """
+
+ options: list[SchemaNode | None]
+
+
+
+
+[docs]
+@frozen
+class ObjectField:
+ r"""Field in an object schema.
+
+ Arguments:
+ name: Name of the field.
+ schema: Schema of the field.
+ hint: Hint extracted from the docstring.
+ required: Whether the field is optional or required.
+ """
+
+ name: str
+ schema: SchemaNode
+ hint: str | None
+ required: bool
+
+
+
+
+[docs]
+@frozen
+class ObjectNode(SchemaNode):
+ r"""Node corresponding to composite types.
+
+ Represents the signature of the constructor.
+
+ Attributes:
+ name: Name of the object.
+ fields: The fields of the object.
+ hint: Hint extracted from the docstring.
+ """
+
+ name: str
+ fields: list[ObjectField]
+ hint: str | None
+
+
+
+
+[docs]
+@frozen
+class CallableSchema:
+ r"""Complete schema of a function of a class.
+
+ Attributes:
+ call_schema: Schema of the function call.
+ return_schema: Schema of the return value.
+ None if the function returns None.
+ """
+
+ call_schema: ObjectNode
+ return_schema: SchemaNode
+
+
Complete schema of a function of a class.
+Schema of the function call.
+Schema of the return value. +None if the function returns None.
+Node corresponding to generic mapping types.
+The mapping type (e.g. dict or collections.defaultdict).
+type[collections.abc.Mapping]
+The schema of the keys in the mapping.
+The schema of the values in the mapping.
+Field in an object schema.
+name – Name of the field.
schema – Schema of the field.
hint – Hint extracted from the docstring.
required – Whether the field is optional or required.
Node corresponding to composite types.
+Represents the signature of the constructor.
+Name of the object.
+str
+The fields of the object.
+Hint extracted from the docstring.
+str | None
+Node corresponding to primitive types.
+Primitime types are int, float, str, and bool.
+The primitive type.
+type[int] | type[float] | type[str] | type[bool]
+(Does not include tuples and strings)
+The sequence type.
+type[collections.abc.Sequence]
+The schema of the items in the sequence.
+Node corresponding to tuples and named tuples.
+The tuple type (tuple, or a subclass of typing.NamedTuple).
+type[tuple]
+The schemas of the items in the tuple.
+Node corresponding to missing, Any, or None annotations.
+None if the annotation is None.
+Node corresponding to Union and Optional types.
+The schemas of the options in the union. +May be None in case of optional types.
+list[wanga.schema.schema.SchemaNode | None]
+Extract schema from the type annotation.
+Extract schema from a callable.
++ |
+ | + |
+ |
+ |
+ |
+ | + |
+ |
|
+
+ |
+ | + |
+ | + |
+ |
+ | + |
+ | + |
+ | + |
+ |
+ |
|
+
+ w | ||
+ |
+ wanga | + |
+ |
+ wanga.schema.extract | + |
+ |
+ wanga.schema.schema | + |