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