Skip to content

SQLAlchemyObjectType{Meta} should be more friendly to subclassing implementations. #37

Closed
@dewiniaid

Description

@dewiniaid

Currently, SQLAlchemyObjectTypeMeta has the following bits that complicate attempts at subclassing it to extend functionality:

  1. 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)
  1. 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 from attrs['Meta'] beforehand and doing setattr(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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions