Is your feature request related to a problem? Please describe.
I've been working on some extensions which has been fun (thanks for griffe ❤️)
However, a stumbling block has been that the griffe.Object.is_* properties aren't playing nicely with type checkers:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING :
import griffe
def on_module (mod : griffe .Module ) -> None :
for member in mod .members .values ():
if member .is_class :
_bases = member .bases
elif member .is_module :
_imports_future_annotations = member .imports_future_annotations
Show me red squiggles
Describe the solution you'd like
I like the color red, but not in my IDE.
Luckily, the typing spec has blessed is with (Type Narrowing ) which can help avoid them:
from __future__ import annotations
from typing import TYPE_CHECKING , Any
import griffe
if TYPE_CHECKING :
import sys
if sys .version_info >= (3 , 13 ):
from typing import TypeIs
else :
from typing_extensions import TypeIs
def is_class (obj : Any ) -> TypeIs [griffe .Class ]:
return obj is griffe .Kind .CLASS
def is_module (obj : Any ) -> TypeIs [griffe .Module ]:
return obj is griffe .Kind .MODULE
def on_module (mod : griffe .Module ) -> None :
for member in mod .members .values ():
if is_class (member ):
_bases = member .bases
elif is_module (member ):
_imports_future_annotations = member .imports_future_annotations
Show me happy typing
Note
is_module and is_class are simple examples - the idea can extend to any kind of narrowing you'd use with griffe
Ideally , these guards would be accessible either at the top-level or their own namespace:
griffe.is_*
griffe.<somewhere-else>.is_*
Examples from elsewhere:
Describe alternatives you've considered
Ignore the issue
Use # <type-checker>: ignore[...]
Use typing.cast
Breaks exhaustiveness checking
Need to be careful that the target type is correct (can't rely on the type checker's help)
Avoid the issue
Use isinstance (or identity checks) inline
Rolling your own TypeIs guards
This is what I'm doing now
In theory, keeping in sync with griffe may be a concern
Doing this in a low-overhead way is usually best understood by the author
E.g. I'd need to read the source and find out about Kind to know that's a cheaper check than isinstance
But wait, can't we just define these on the existing properties?
I wish 😔, more details here .
In short, you can define a method to be typeguard - but the narrowing applies to the first non- self|cls argument.
The properties don't fit into this narrow window sadly
Additional context
I did a quick search for ignore, and found these guys that demonstrate the issue:
Show examples within griffe
A healthy chunk of mixins.py
if member .is_attribute :
member = cast ("Attribute" , member )
if not member .is_alias and member .is_class :
_apply_recursively (member , processed ) # ty:ignore[invalid-argument-type]
if not member .is_alias and (member .is_module or member .is_class ):
_apply_recursively (member , processed ) # ty:ignore[invalid-argument-type]
if self .members [name ].is_alias :
return self .members [name ].target_path # ty:ignore[unresolved-attribute]
while target .is_alias :
if target .path in paths_seen :
raise CyclicAliasError ([* paths_seen , target .path ])
paths_seen [target .path ] = None
target = target .target # ty:ignore[unresolved-attribute]
return target # ty:ignore[invalid-return-type]
and self .members ["annotations" ].is_alias
and self .members ["annotations" ].target_path == "__future__.annotations" # ty:ignore[unresolved-attribute]
bases : list [Class ] = [base for base in self .resolved_bases if base .is_class ] # ty:ignore[invalid-assignment]
Is your feature request related to a problem? Please describe.
I've been working on some extensions which has been fun (thanks for
griffe❤️)However, a stumbling block has been that the
griffe.Object.is_*properties aren't playing nicely with type checkers:Show me red squiggles
Describe the solution you'd like
I like the color red, but not in my IDE.
Luckily, the typing spec has blessed is with (Type Narrowing) which can help avoid them:
Show me happy typing
Note
is_moduleandis_classare simple examples - the idea can extend to any kind of narrowing you'd use withgriffeIdeally, these guards would be accessible either at the top-level or their own namespace:
griffe.is_*griffe.<somewhere-else>.is_*Examples from elsewhere:
narwhals.dependenciesinspect.pyi(stdlib)pyarrow.typestyping_inspection.typing_objectsDescribe alternatives you've considered
# <type-checker>: ignore[...]typing.castisinstance(or identity checks) inlineTypeIsguardsgriffemay be a concernKindto know that's a cheaper check thanisinstanceBut wait, can't we just define these on the existing properties?
I wish 😔, more details here.
In short, you can define a method to be typeguard - but the narrowing applies to the first non-
self|clsargument.The properties don't fit into this narrow window sadly
Additional context
I did a quick search for
ignore, and found these guys that demonstrate the issue:Show examples within
griffeA healthy chunk of
mixins.pygriffe/packages/griffelib/src/griffe/_internal/extensions/dataclasses.py
Lines 80 to 81 in 5dc97b7
griffe/packages/griffelib/src/griffe/_internal/extensions/dataclasses.py
Lines 212 to 213 in 5dc97b7
griffe/packages/griffelib/src/griffe/_internal/extensions/dataclasses.py
Lines 216 to 217 in 5dc97b7
griffe/packages/griffelib/src/griffe/_internal/models.py
Lines 1225 to 1226 in 5dc97b7
griffe/packages/griffelib/src/griffe/_internal/models.py
Lines 2111 to 2116 in 5dc97b7
griffe/packages/griffelib/src/griffe/_internal/models.py
Lines 2275 to 2276 in 5dc97b7
griffe/packages/griffelib/src/griffe/_internal/models.py
Line 2453 in 5dc97b7