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.
ariadne.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.