1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
"""Tests for the `encoders` module."""
from __future__ import annotations
import json
import sys
import pytest
from jsonschema import ValidationError, validate
from griffe import (
Attribute,
Class,
Function,
GriffeLoader,
Kind,
Module,
Object,
temporary_inspected_package,
temporary_visited_module,
)
def test_minimal_data_is_enough() -> None:
"""Test serialization and de-serialization.
This is an end-to-end test that asserts
we can load back a serialized tree and
infer as much data as within the original tree.
"""
loader = GriffeLoader()
module = loader.load("griffe")
dump_options = {"indent": 2, "sort_keys": True}
minimal = module.as_json(full=False, **dump_options)
full = module.as_json(full=True, **dump_options)
reloaded = Module.from_json(minimal)
assert reloaded.as_json(full=False, **dump_options) == minimal
assert reloaded.as_json(full=True, **dump_options) == full
# Also works (but will result in a different type hint).
assert Object.from_json(minimal)
# Won't work if the JSON doesn't represent the type requested.
with pytest.raises(TypeError, match="provided JSON object is not of type"):
Function.from_json(minimal)
def test_namespace_packages() -> None:
"""Test support for namespace packages.
Namespace packages are a bit special as they have no `__init__.py` file.
"""
with temporary_inspected_package("namespace_package", init=False) as package:
dump_options = {"indent": 2, "sort_keys": True}
as_json = package.as_json(full=True, **dump_options)
from_json = Object.from_json(as_json)
assert from_json.as_json(full=True, **dump_options) == as_json
@pytest.mark.parametrize(
"symbol",
[
# Attribute.
"_internal.loader.GriffeLoader.finder",
# Function/method.
"_internal.loader.GriffeLoader.load",
# Class.
"_internal.mixins.GetMembersMixin",
# Module.
"_internal.debug",
],
)
def test_minimal_light_data_is_enough(symbol: str) -> None:
"""Test serialization and de-serialization."""
loader = GriffeLoader()
package = loader.load("griffe")
obj = package[symbol]
dump_options = {"indent": 2, "sort_keys": True}
minimal = obj.as_json(full=False, **dump_options)
full = obj.as_json(full=True, **dump_options)
reloaded = {
Kind.MODULE: Module,
Kind.CLASS: Class,
Kind.FUNCTION: Function,
Kind.ATTRIBUTE: Attribute,
}[obj.kind].from_json(minimal) # type: ignore[attr-defined]
reloaded.parent = obj.parent
assert reloaded.as_json(full=False, **dump_options) == minimal
assert reloaded.as_json(full=True, **dump_options) == full
# YORE: EOL 3.12: Remove block.
# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 generics")
def test_encoding_pep695_generics_without_defaults() -> None:
"""Test serialization and de-serialization of PEP 695 generics without defaults.
Defaults are only possible from Python 3.13 onwards.
"""
with temporary_visited_module(
"""
class Class[X: Exception]: pass
def func[**P, T, *R](arg: T, *args: P.args, **kwargs: P.kwargs) -> tuple[*R]: pass
type TA[T: (int, str)] = dict[str, T]
""",
) as module:
minimal = module.as_json(full=False)
full = module.as_json(full=True)
reloaded = Module.from_json(minimal)
assert reloaded.as_json(full=False) == minimal
assert reloaded.as_json(full=True) == full
# Also works (but will result in a different type hint).
assert Object.from_json(minimal)
# Won't work if the JSON doesn't represent the type requested.
with pytest.raises(TypeError, match="provided JSON object is not of type"):
Function.from_json(minimal)
# YORE: EOL 3.12: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 13), reason="Python less than 3.13 does not have defaults in PEP 695 generics") # fmt: skip
def test_encoding_pep695_generics() -> None:
"""Test serialization and de-serialization of PEP 695 generics with defaults.
Defaults are only possible from Python 3.13 onwards.
"""
with temporary_visited_module(
"""
class Class[X: Exception = OSError]: pass
def func[**P, T, *R](arg: T, *args: P.args, **kwargs: P.kwargs) -> tuple[*R]: pass
type TA[T: (int, str) = str] = dict[str, T]
""",
) as module:
minimal = module.as_json(full=False)
full = module.as_json(full=True)
reloaded = Module.from_json(minimal)
assert reloaded.as_json(full=False) == minimal
assert reloaded.as_json(full=True) == full
# Also works (but will result in a different type hint).
assert Object.from_json(minimal)
# Won't work if the JSON doesn't represent the type requested.
with pytest.raises(TypeError, match="provided JSON object is not of type"):
Function.from_json(minimal)
# use this function in test_json_schema to ease schema debugging
def _validate(obj: dict, schema: dict) -> None:
if "members" in obj:
for member in obj["members"]:
_validate(member, schema)
try:
validate(obj, schema)
except ValidationError:
print(obj["path"]) # noqa: T201
raise
def test_json_schema() -> None:
"""Assert that our serialized data matches our JSON schema."""
loader = GriffeLoader()
module = loader.load("griffe")
loader.resolve_aliases()
data = json.loads(module.as_json(full=True))
with open("docs/schema.json", encoding="utf8") as f: # noqa: PTH123
schema = json.load(f)
validate(data, schema)
# YORE: EOL 3.12: Remove block.
# YORE: EOL 3.11: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 12), reason="Python less than 3.12 does not have PEP 695 generics")
def test_json_schema_for_pep695_generics_without_defaults() -> None:
"""Assert that serialized PEP 695 generics without defaults match our JSON schema.
Defaults are only possible from Python 3.13 onwards.
"""
with temporary_visited_module(
"""
class Class[X: Exception]: pass
def func[**P, T, *R](arg: T, *args: P.args, **kwargs: P.kwargs) -> tuple[*R]: pass
type TA[T: (int, str)] = dict[str, T]
""",
) as module:
data = json.loads(module.as_json(full=True))
with open("docs/schema.json", encoding="utf8") as f: # noqa: PTH123
schema = json.load(f)
validate(data, schema)
# YORE: EOL 3.12: Remove line.
@pytest.mark.skipif(sys.version_info < (3, 13), reason="Python less than 3.13 does not have defaults in PEP 695 generics") # fmt: skip
def test_json_schema_for_pep695_generics() -> None:
"""Assert that serialized PEP 695 generics with defaults match our JSON schema.
Defaults are only possible from Python 3.13 onwards.
"""
with temporary_visited_module(
"""
class Class[X: Exception = OSError]: pass
def func[**P, T, *R](arg: T, *args: P.args, **kwargs: P.kwargs) -> tuple[*R]: pass
type TA[T: (int, str) = str] = dict[str, T]
""",
) as module:
data = json.loads(module.as_json(full=True))
with open("docs/schema.json", encoding="utf8") as f: # noqa: PTH123
schema = json.load(f)
validate(data, schema)
|