ConradLinus's picture
Upload folder using huggingface_hub
d631808 verified
# 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