You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If using a non-literal value as the description argument to a Pydantic Field, griffe will crash when trying to parse it using ast.literal_eval.
To Reproduce
import griffe
static_model = """
import pydantic
class TestModel(pydantic.BaseModel):
abc: str = pydantic.Field(description="xyz")
"""
with griffe.temporary_visited_package(
"package",
modules={"__init__.py": static_model},
extensions=griffe.load_extensions("griffe_pydantic"),
) as package:
assert package["TestModel"]["abc"].docstring.value == "xyz"
dynamic_model = """
import pydantic
desc = "xyz"
class TestModel(pydantic.BaseModel):
abc: str = pydantic.Field(description=desc)
"""
with griffe.temporary_visited_package(
"package",
modules={"__init__.py": dynamic_model},
extensions=griffe.load_extensions("griffe_pydantic"),
) as package:
assert package["TestModel"]["abc"].docstring.value == "xyz"
Full traceback
Full traceback
---------------------------------------------------------------------------ValueErrorTraceback (mostrecentcalllast)
CellIn[23], line2615assertpackage["TestModel"]["abc"].docstring.value=="xyz"17dynamic_model=""" 18 import pydantic 19 (...) 23 abc: str = pydantic.Field(description=desc) 24 """--->26withgriffe.temporary_visited_package(
27"package",
28modules={"__init__.py": dynamic_model},
29extensions=griffe.load_extensions("griffe_pydantic"),
30 ) aspackage:
31assertpackage["TestModel"]["abc"].docstring.value=="xyz"File [...]\Lib\contextlib.py:137, in_GeneratorContextManager.__enter__(self)
135delself.args, self.kwds, self.func136try:
-->137returnnext(self.gen)
138exceptStopIteration:
139raiseRuntimeError("generator didn't yield") fromNoneFile [...]\Lib\site-packages\_griffe\tests.py:167, intemporary_visited_package(package, modules, init, inits, extensions, docstring_parser, docstring_options, lines_collection, modules_collection, allow_inspection, store_source, resolve_aliases, resolve_external, resolve_implicit)
137"""Create and visit a temporary package. 138 139 Parameters: (...) 164 A module. 165 """166withtemporary_pypackage(package, modules, init=init, inits=inits) astmp_package:
-->167yieldload( # type: ignore[misc]168tmp_package.name,
169search_paths=[tmp_package.tmpdir],
170extensions=extensions,
171docstring_parser=docstring_parser,
172docstring_options=docstring_options,
173lines_collection=lines_collection,
174modules_collection=modules_collection,
175allow_inspection=allow_inspection,
176store_source=store_source,
177resolve_aliases=resolve_aliases,
178resolve_external=resolve_external,
179resolve_implicit=resolve_implicit,
180force_inspection=False,
181 )
File [...]\Lib\site-packages\_griffe\loader.py:811, inload(objspec, submodules, try_relative_path, extensions, search_paths, docstring_parser, docstring_options, lines_collection, modules_collection, allow_inspection, force_inspection, store_source, find_stubs_package, resolve_aliases, resolve_external, resolve_implicit)
737"""Load and return a Griffe object. 738 739 In Griffe's context, loading means: (...) 798 A Griffe object. 799 """800loader=GriffeLoader(
801extensions=extensions,
802search_paths=search_paths,
(...)
809store_source=store_source,
810 )
-->811result=loader.load(
812objspec,
813submodules=submodules,
814try_relative_path=try_relative_path,
815find_stubs_package=find_stubs_package,
816 )
817ifresolve_aliases:
818loader.resolve_aliases(implicit=resolve_implicit, external=resolve_external)
File [...]\Lib\site-packages\_griffe\loader.py:186, inGriffeLoader.load(self, objspec, submodules, try_relative_path, find_stubs_package)
183logger.exception("Could not load package %s", package)
184raise-->186returnself._post_load(top_module, obj_path)
File [...]\Lib\site-packages\_griffe\loader.py:212, inGriffeLoader._post_load(self, module, obj_path)
210# Package is loaded, we now retrieve the initially requested object and return it.211obj=self.modules_collection.get_member(obj_path)
-->212self.extensions.call("on_package_loaded", pkg=module, loader=self)
213returnobjFile [...]\Lib\site-packages\_griffe\extensions\base.py:313, inExtensions.call(self, event, **kwargs)
306"""Call the extension hook for the given event. 307 308 Parameters: 309 event: The triggered event. 310 **kwargs: Arguments passed to the hook. 311 """312forextensioninself._extensions:
-->313getattr(extension, event)(**kwargs)
File [...]\Lib\site-packages\griffe_pydantic\extension.py:42, inPydanticExtension.on_package_loaded(self, pkg, **kwargs)
40defon_package_loaded(self, *, pkg: Module, **kwargs: Any) ->None: # noqa: ARG00241"""Detect models once the whole package is loaded."""--->42static.process_module(pkg, processed=self.processed, schema=self.schema)
File [...]\Lib\site-packages\griffe_pydantic\static.py:167, inprocess_module(mod, processed, schema)
164forclsinmod.classes.values():
165# Don't process aliases, real classes will be processed at some point anyway.166ifnotcls.is_alias:
-->167process_class(cls, processed=processed, schema=schema)
169forsubmoduleinmod.modules.values():
170process_module(submodule, processed=processed, schema=schema)
File [...]\Lib\site-packages\griffe_pydantic\static.py:146, inprocess_class(cls, processed, schema)
144formemberincls.all_members.values():
145ifisinstance(member, Attribute):
-->146process_attribute(member, cls, processed=processed)
147elifisinstance(member, Function):
148process_function(member, cls, processed=processed)
File [...]\Lib\site-packages\griffe_pydantic\static.py:100, inprocess_attribute(attr, cls, processed)
98# Populate docstring from the field's `description` argument.99ifnotattr.docstringand (docstring:=kwargs.get("description", None)):
-->100attr.docstring=Docstring(ast.literal_eval(docstring), parent=attr)
File [...]\Lib\ast.py:110, inliteral_eval(node_or_string)
108returnleft-right109return_convert_signed_num(node)
-->110return_convert(node_or_string)
File [...]\Lib\ast.py:109, inliteral_eval.<locals>._convert(node)
107else:
108returnleft-right-->109return_convert_signed_num(node)
File [...]\Lib\ast.py:83, inliteral_eval.<locals>._convert_signed_num(node)
81else:
82return-operand--->83return_convert_num(node)
File [...]\Lib\ast.py:74, inliteral_eval.<locals>._convert_num(node)
72def_convert_num(node):
73ifnotisinstance(node, Constant) ortype(node.value) notin (int, float, complex):
--->74_raise_malformed_node(node)
75returnnode.valueFile [...]\Lib\ast.py:71, inliteral_eval.<locals>._raise_malformed_node(node)
69iflno:=getattr(node, 'lineno', None):
70msg+=f' on line {lno}'--->71raiseValueError(msg+f': {node!r}')
ValueError: malformednodeorstring: ExprName(name='desc', parent=Class('TestModel', 6, 7))
Expected behavior
Either an error message that explains the dynamic description is not supported before halting, a warning and an empty docstring, or some kind of dynamic resolution of the description attribute.
Description of the bug
If using a non-literal value as the
description
argument to a PydanticField
, griffe will crash when trying to parse it usingast.literal_eval
.To Reproduce
Full traceback
Full traceback
Expected behavior
Either an error message that explains the dynamic description is not supported before halting, a warning and an empty docstring, or some kind of dynamic resolution of the description attribute.
Environment information
griffe-pydantic
v1.1.0Additional context
Sibling to feature request here.
The text was updated successfully, but these errors were encountered: