Coverage for tests/test_extension.py: 100.00%
34 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-18 01:11 +0100
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-18 01:11 +0100
1"""Tests for the `extension` module."""
3from __future__ import annotations
5import logging
6from typing import TYPE_CHECKING
8from griffe import Extensions, temporary_visited_package
10from griffe_pydantic.extension import PydanticExtension
12if TYPE_CHECKING:
13 import pytest
14 from mkdocstrings_handlers.python.handler import PythonHandler
17code = """
18 from pydantic import field_validator, ConfigDict, BaseModel, Field
21 class ExampleParentModel(BaseModel):
22 '''An example parent model.'''
23 parent_field: str = Field(..., description="Parent field.")
26 class ExampleModel(ExampleParentModel):
27 '''An example child model.'''
29 model_config = ConfigDict(frozen=False)
31 field_without_default: str
32 '''Shows the *[Required]* marker in the signature.'''
34 field_plain_with_validator: int = 100
35 '''Show standard field with type annotation.'''
37 field_with_validator_and_alias: str = Field("FooBar", alias="BarFoo", validation_alias="BarFoo")
38 '''Shows corresponding validator with link/anchor.'''
40 field_with_constraints_and_description: int = Field(
41 default=5, ge=0, le=100, description="Shows constraints within doc string."
42 )
44 @field_validator("field_with_validator_and_alias", "field_plain_with_validator", mode="before")
45 @classmethod
46 def check_max_length_ten(cls, v):
47 '''Show corresponding field with link/anchor.'''
48 if len(v) >= 10:
49 raise ValueError("No more than 10 characters allowed")
50 return v
52 def regular_method(self):
53 pass
56 class RegularClass(object):
57 regular_attr = 1
58"""
61def test_extension() -> None:
62 """Test the extension."""
63 with temporary_visited_package(
64 "package",
65 modules={"__init__.py": code},
66 extensions=Extensions(PydanticExtension(schema=True)),
67 ) as package:
68 assert package
70 assert "ExampleParentModel" in package.classes
71 assert package.classes["ExampleParentModel"].labels == {"pydantic-model"}
73 assert "ExampleModel" in package.classes
74 assert package.classes["ExampleModel"].labels == {"pydantic-model"}
76 config = package.classes["ExampleModel"].extra["griffe_pydantic"]["config"]
77 assert config == {"frozen": "False"}
79 schema = package.classes["ExampleModel"].extra["griffe_pydantic"]["schema"]
80 assert schema.startswith('{\n "description"')
83def test_imported_models() -> None:
84 """Test the extension with imported models."""
85 with temporary_visited_package(
86 "package",
87 modules={
88 "__init__.py": "from ._private import MyModel\n\n__all__ = ['MyModel']",
89 "_private.py": "from pydantic import BaseModel\n\nclass MyModel(BaseModel):\n field1: str\n '''Some field.'''\n",
90 },
91 extensions=Extensions(PydanticExtension(schema=False)),
92 ) as package:
93 assert package["MyModel"].labels == {"pydantic-model"}
94 assert package["MyModel.field1"].labels == {"pydantic-field"}
97def test_rendering_model_config_using_configdict(python_handler: PythonHandler) -> None:
98 """Test the extension with model config using ConfigDict."""
99 code = """
100 from pydantic import BaseModel, ConfigDict, Field
102 class Model(BaseModel):
103 usage: str | None = Field(
104 None,
105 description="Some description.",
106 example="Some example.",
107 )
108 model_config = ConfigDict(
109 json_schema_extra={
110 "example": {
111 "usage": "Some usage.",
112 "limitations": "Some limitations.",
113 "billing": "Some value.",
114 "notice_period": "Some value.",
115 }
116 }
117 )
118 """
119 with temporary_visited_package(
120 "package",
121 modules={"__init__.py": code},
122 extensions=Extensions(PydanticExtension(schema=False)),
123 ) as package:
124 python_handler.render(package["Model"], python_handler.get_options({})) # Assert no errors.
127def test_not_crashing_on_dynamic_field_description(caplog: pytest.LogCaptureFixture) -> None:
128 """Test the extension with dynamic field description."""
129 code = """
130 import pydantic
132 desc = "xyz"
134 class TestModel(pydantic.BaseModel):
135 abc: str = pydantic.Field(description=desc)
136 """
137 with (
138 caplog.at_level(logging.DEBUG),
139 temporary_visited_package(
140 "package",
141 modules={"__init__.py": code},
142 extensions=Extensions(PydanticExtension(schema=False)),
143 ),
144 ):
145 assert any(
146 record.levelname == "DEBUG" and "field 'package.TestModel.abc' as literal" in record.message
147 for record in caplog.records
148 )
151def test_ignore_classvars() -> None:
152 """Test the extension ignores class variables."""
153 code = """
154 from pydantic import BaseModel
155 from typing import ClassVar
157 class Model(BaseModel):
158 field: str
159 class_var: ClassVar[int] = 1
160 """
161 with temporary_visited_package(
162 "package",
163 modules={"__init__.py": code},
164 extensions=Extensions(PydanticExtension(schema=False)),
165 ) as package:
166 assert "pydantic-field" not in package["Model.class_var"].labels
167 assert "class-attribute" in package["Model.class_var"].labels