I'm using the Python package ariadne, v0.23.0.
I wrote a utility to scan my code for instances of ariadne.types.SchemaBindable
, but it's also unintentionally picking up the SchemaBindable
subclasses that I've imported:
ariadne.input.InputType( SchemaBindable )
ariadne.objects.ObjectType( SchemaBindable )
ariadne.scalars.ScalarType( SchemaBindable )
ariadne.unions.UnionType( SchemaBindable )
I ran a test in a Python shell, and sure enough, isinstance()
is returning True
when comparing those classes to SchemaBindable
:
isinstance( ObjectType, SchemaBindable ) -> True
...etc...
SchemaBindable
even appears to be an instance of itself:
isinstance( SchemaBindable, SchemaBindable ) -> True
Meanwhile, issubclass()
continues to also return True
:
issubclass( ObjectType, SchemaBindable ) -> True
Make it make sense.
I'm using the Python package ariadne, v0.23.0.
I wrote a utility to scan my code for instances of ariadne.types.SchemaBindable
, but it's also unintentionally picking up the SchemaBindable
subclasses that I've imported:
ariadne.input.InputType( SchemaBindable )
ariadne.objects.ObjectType( SchemaBindable )
ariadne.scalars.ScalarType( SchemaBindable )
ariadne.unions.UnionType( SchemaBindable )
I ran a test in a Python shell, and sure enough, isinstance()
is returning True
when comparing those classes to SchemaBindable
:
isinstance( ObjectType, SchemaBindable ) -> True
...etc...
SchemaBindable
even appears to be an instance of itself:
isinstance( SchemaBindable, SchemaBindable ) -> True
Meanwhile, issubclass()
continues to also return True
:
issubclass( ObjectType, SchemaBindable ) -> True
Make it make sense.
Share Improve this question asked Feb 5 at 23:57 odigityodigity 8,1766 gold badges41 silver badges58 bronze badges 2- See stackoverflow.com/questions/67355992/… – PM 77-1 Commented Feb 6 at 0:02
- @PM77-1 That doesn't address my question. – odigity Commented Feb 6 at 0:29
3 Answers
Reset to default 1ariadne.types.SchemaBindable
is documented as a regular class that you're supposed to extend to create bindables, but it's actually implemented as a protocol, and marked runtime-checkable:
@runtime_checkable
class SchemaBindable(Protocol):
# docstring omitted because it's long
def bind_to_schema(self, schema: GraphQLSchema) -> None:
"""Binds this `SchemaBindable` instance to the instance of GraphQL schema."""
Runtime-checkable protocols use a metaclass __instancecheck__
method to customize isinstance
checks. Any object that has the attributes specified by the protocol will be considered an instance of the protocol.
But if you write a subclass of SchemaBindable
:
class YourBindable(SchemaBindable):
def bind_to_schema(self, schema):
# implementation here
then that subclass has a bind_to_schema
attribute, and the checker logic doesn't care that that attribute is meant to be a method implementation for instances of YourBindable
. It just checks that the attribute exists, and if the attribute is supposed to be callable, it checks that the attribute isn't None
:
for attr in cls.__protocol_attrs__:
try:
val = getattr_static(instance, attr)
except AttributeError:
break
# this attribute is set by @runtime_checkable:
if val is None and attr not in cls.__non_callable_proto_members__:
break
else:
return True
return False
So that means that subclasses of SchemaBindable
get treated as instances of SchemaBindable
, even though they probably shouldn't be.
so, what is exactly your problem?
isinstance
just won't lie to you: Ariadne is a special library which not only have a class hierarchy,where those other classes do inherit from SchemaBindable, but in order to do its thing, it also needs that the SchemaBindable class counts as an instance of itself. That is possible in Python, for example, by customizing the __subclasscheck__
method .
And then it just happens that everything you list is, in fact, an instance of SchemaBindable
. Maybe you need to add some othe way, in your test, to know if what you are looking at is a class you wrote - there are several ways to do that, but since you didn't include examples of your code (not from the actual classes you want to test and neither from your utility), there is little we can help further with.
This behavior occurs because SchemaBindable
in Ariadne is likely implemented as a metaclass, not a regular class. Metaclasses are classes for classes - they define how classes behave.
When a class uses a metaclass:
- The class itself becomes an instance of the metaclass
- The class also remains a subclass of the metaclass
This explains why both isinstance()
and issubclass()
return True
, and why SchemaBindable
appears to be an instance of itself.
To fix your scanning utility, you need to use issubclass()
exclusively and add a check to filter out SchemaBindable
itself.
Here's how to modify your scanning to only get subclasses, excluding SchemaBindable
itself:
Instead of isinstance() check, use this:
if issubclass(type_, SchemaBindable) and type_ != SchemaBindable:
# Found a subclass that isn't SchemaBindable itself
pass
This will only match ObjectType, InputType, ScalarType, UnionType, etc., but not SchemaBindable
itself.