Spaces:
Running
Running
# This module contains data encoders/serializers and decoders/deserializers. | |
# We only support JSON for now, but might want to add more formats in the future. | |
from __future__ import annotations | |
import json | |
from pathlib import Path, PosixPath, WindowsPath | |
from typing import Any, Callable | |
from _griffe import expressions | |
from _griffe.enumerations import Kind, ParameterKind | |
from _griffe.models import ( | |
Alias, | |
Attribute, | |
Class, | |
Decorator, | |
Docstring, | |
Function, | |
Module, | |
Object, | |
Parameter, | |
Parameters, | |
) | |
_json_encoder_map: dict[type, Callable[[Any], Any]] = { | |
Path: str, | |
PosixPath: str, | |
WindowsPath: str, | |
set: sorted, | |
} | |
class JSONEncoder(json.JSONEncoder): | |
"""JSON encoder. | |
JSON encoders can be used directly, or through | |
the [`json.dump`][] or [`json.dumps`][] methods. | |
Examples: | |
>>> from griffe import JSONEncoder | |
>>> JSONEncoder(full=True).encode(..., **kwargs) | |
>>> import json | |
>>> from griffe import JSONEncoder | |
>>> json.dumps(..., cls=JSONEncoder, full=True, **kwargs) | |
""" | |
def __init__( | |
self, | |
*args: Any, | |
full: bool = False, | |
**kwargs: Any, | |
) -> None: | |
"""Initialize the encoder. | |
Parameters: | |
*args: See [`json.JSONEncoder`][]. | |
full: Whether to dump full data or base data. | |
If you plan to reload the data in Python memory | |
using the [`json_decoder`][griffe.json_decoder], | |
you don't need the full data as it can be inferred again | |
using the base data. If you want to feed a non-Python | |
tool instead, dump the full data. | |
**kwargs: See [`json.JSONEncoder`][]. | |
""" | |
super().__init__(*args, **kwargs) | |
self.full: bool = full | |
"""Whether to dump full data or base data.""" | |
def default(self, obj: Any) -> Any: | |
"""Return a serializable representation of the given object. | |
Parameters: | |
obj: The object to serialize. | |
Returns: | |
A serializable representation. | |
""" | |
try: | |
return obj.as_dict(full=self.full) | |
except AttributeError: | |
return _json_encoder_map.get(type(obj), super().default)(obj) | |
def _load_docstring(obj_dict: dict) -> Docstring | None: | |
if "docstring" in obj_dict: | |
return Docstring(**obj_dict["docstring"]) | |
return None | |
def _load_decorators(obj_dict: dict) -> list[Decorator]: | |
return [Decorator(**dec) for dec in obj_dict.get("decorators", [])] | |
def _load_expression(expression: dict) -> expressions.Expr: | |
# The expression class name is stored in the `cls` key-value. | |
cls = getattr(expressions, expression.pop("cls")) | |
expr = cls(**expression) | |
# For attributes, we need to re-attach names (`values`) together, | |
# as a single linked list, from right to left: | |
# in `a.b.c`, `c` links to `b` which links to `a`. | |
# In `(a or b).c` however, `c` does not link to `(a or b)`, | |
# as `(a or b)` is not a name and wouldn't allow to resolve `c`. | |
if cls is expressions.ExprAttribute: | |
previous = None | |
for value in expr.values: | |
if previous is not None: | |
value.parent = previous | |
if isinstance(value, expressions.ExprName): | |
previous = value | |
return expr | |
def _load_parameter(obj_dict: dict[str, Any]) -> Parameter: | |
return Parameter( | |
obj_dict["name"], | |
annotation=obj_dict["annotation"], | |
kind=ParameterKind(obj_dict["kind"]), | |
default=obj_dict["default"], | |
docstring=_load_docstring(obj_dict), | |
) | |
def _attach_parent_to_expr(expr: expressions.Expr | str | None, parent: Module | Class) -> None: | |
if not isinstance(expr, expressions.Expr): | |
return | |
for elem in expr: | |
if isinstance(elem, expressions.ExprName): | |
elem.parent = parent | |
elif isinstance(elem, expressions.ExprAttribute) and isinstance(elem.first, expressions.ExprName): | |
elem.first.parent = parent | |
def _attach_parent_to_exprs(obj: Class | Function | Attribute, parent: Module | Class) -> None: | |
# Every name and attribute expression must be reattached | |
# to its parent Griffe object (using its `parent` attribute), | |
# to allow resolving names. | |
if isinstance(obj, Class): | |
if obj.docstring: | |
_attach_parent_to_expr(obj.docstring.value, parent) | |
for decorator in obj.decorators: | |
_attach_parent_to_expr(decorator.value, parent) | |
elif isinstance(obj, Function): | |
if obj.docstring: | |
_attach_parent_to_expr(obj.docstring.value, parent) | |
for decorator in obj.decorators: | |
_attach_parent_to_expr(decorator.value, parent) | |
for param in obj.parameters: | |
_attach_parent_to_expr(param.annotation, parent) | |
_attach_parent_to_expr(param.default, parent) | |
_attach_parent_to_expr(obj.returns, parent) | |
elif isinstance(obj, Attribute): | |
if obj.docstring: | |
_attach_parent_to_expr(obj.docstring.value, parent) | |
_attach_parent_to_expr(obj.value, parent) | |
def _load_module(obj_dict: dict[str, Any]) -> Module: | |
module = Module(name=obj_dict["name"], filepath=Path(obj_dict["filepath"]), docstring=_load_docstring(obj_dict)) | |
# YORE: Bump 2: Replace line with `members = obj_dict.get("members", {}).values()`. | |
members = obj_dict.get("members", []) | |
# YORE: Bump 2: Remove block. | |
if isinstance(members, dict): | |
members = members.values() | |
for module_member in members: | |
module.set_member(module_member.name, module_member) | |
_attach_parent_to_exprs(module_member, module) | |
module.labels |= set(obj_dict.get("labels", ())) | |
return module | |
def _load_class(obj_dict: dict[str, Any]) -> Class: | |
class_ = Class( | |
name=obj_dict["name"], | |
lineno=obj_dict["lineno"], | |
endlineno=obj_dict.get("endlineno"), | |
docstring=_load_docstring(obj_dict), | |
decorators=_load_decorators(obj_dict), | |
bases=obj_dict["bases"], | |
) | |
# YORE: Bump 2: Replace line with `members = obj_dict.get("members", {}).values()`. | |
members = obj_dict.get("members", []) | |
# YORE: Bump 2: Remove block. | |
if isinstance(members, dict): | |
members = members.values() | |
for class_member in members: | |
class_.set_member(class_member.name, class_member) | |
_attach_parent_to_exprs(class_member, class_) | |
class_.labels |= set(obj_dict.get("labels", ())) | |
_attach_parent_to_exprs(class_, class_) | |
return class_ | |
def _load_function(obj_dict: dict[str, Any]) -> Function: | |
function = Function( | |
name=obj_dict["name"], | |
parameters=Parameters(*obj_dict["parameters"]), | |
returns=obj_dict["returns"], | |
decorators=_load_decorators(obj_dict), | |
lineno=obj_dict["lineno"], | |
endlineno=obj_dict.get("endlineno"), | |
docstring=_load_docstring(obj_dict), | |
) | |
function.labels |= set(obj_dict.get("labels", ())) | |
return function | |
def _load_attribute(obj_dict: dict[str, Any]) -> Attribute: | |
attribute = Attribute( | |
name=obj_dict["name"], | |
lineno=obj_dict["lineno"], | |
endlineno=obj_dict.get("endlineno"), | |
docstring=_load_docstring(obj_dict), | |
value=obj_dict.get("value"), | |
annotation=obj_dict.get("annotation"), | |
) | |
attribute.labels |= set(obj_dict.get("labels", ())) | |
return attribute | |
def _load_alias(obj_dict: dict[str, Any]) -> Alias: | |
return Alias( | |
name=obj_dict["name"], | |
target=obj_dict["target_path"], | |
lineno=obj_dict["lineno"], | |
endlineno=obj_dict.get("endlineno"), | |
) | |
_loader_map: dict[Kind, Callable[[dict[str, Any]], Module | Class | Function | Attribute | Alias]] = { | |
Kind.MODULE: _load_module, | |
Kind.CLASS: _load_class, | |
Kind.FUNCTION: _load_function, | |
Kind.ATTRIBUTE: _load_attribute, | |
Kind.ALIAS: _load_alias, | |
} | |
def json_decoder(obj_dict: dict[str, Any]) -> dict[str, Any] | Object | Alias | Parameter | str | expressions.Expr: | |
"""Decode dictionaries as data classes. | |
The [`json.loads`][] method walks the tree from bottom to top. | |
Examples: | |
>>> import json | |
>>> from griffe import json_decoder | |
>>> json.loads(..., object_hook=json_decoder) | |
Parameters: | |
obj_dict: The dictionary to decode. | |
Returns: | |
An instance of a data class. | |
""" | |
# Load expressions. | |
if "cls" in obj_dict: | |
return _load_expression(obj_dict) | |
# Load objects and parameters. | |
if "kind" in obj_dict: | |
try: | |
kind = Kind(obj_dict["kind"]) | |
except ValueError: | |
return _load_parameter(obj_dict) | |
return _loader_map[kind](obj_dict) | |
# Return dict as is. | |
return obj_dict | |