Description
Currently, SQLAlchemyObjectTypeMeta
has the following bits that complicate attempts at subclassing it to extend functionality:
- This code is the only way to suppress all of the 'metaclass magic':
if not is_base_type(bases, SQLAlchemyObjectTypeMeta):
return type.__new__(cls, name, bases, attrs)
- It's generally a mess if you want to add your own options to the
Meta
class. A subclass can do so by deleting them fromattrs['Meta']
beforehand and doingsetattr(cls._meta, option, value)
after calling the superclass, but that's a bit clunky.
The first limitation becomes an issue when doing things like this:
class MyReplacementSQLAlchemyObjectType(SQLAlchemyObjectType, metaclass=MyReplacementMetaclass):
when the class isn't supposed to map to a model but rather it's intending to add functionality for its own subclasses.
A few possible solutions to this could be:
-
Suppressing the metaclass magic (i.e. returning type.new) if the new class subclasses
AbstractType
-
Suppressing the metaclass magic if the class defines
__abstract__ = True
as an attribute (and deleting the attribute). SQLAlchemy does something like this.
The second issue is something I've seen addressed by having a selectable Options
subclass or factory rather than the hardcoded Options() that exists now. (Marshmallow uses something like this). in This would require some architectural changes in graphene as well though. One implementation of a revamped options might simply be something like:
class Options(object):
setting = "some default value"
setting2 = "another default value"
def __init__(self, **kwargs):
invalid = []
for k, v in kwargs:
if not hasattr(self, k):
invalid.append(k)
else:
setattr(self, k, v)
if invalid:
raise TypeError(
"Invalid attributes: {}".format(', '.join(sorted(kwargs.keys()))))
class SQLAlchemyOptions(Options):
model = None
# ...
# No __init__ required, though it could be used to perform validation rather than having it at the metaclass level.
Then:
-
Options subclasses can simply add class-level variables to add new options (or change defaults of old ones) to those supported.
-
Metaclasses can use attrs['OPTIONS_CLASS'] as the class to use instead of Options. If it's undefined (which it will be 99% of the time), they use the first entry in
bases
to define it instead.
Marshmallow uses an approach similar to this.