flask-resty icon indicating copy to clipboard operation
flask-resty copied to clipboard

Enable better composition model

Open taion opened this issue 7 years ago • 8 comments

For larger APIs, the composition model in Flask-RESTy gets a bit messy. We often end up with multiple parallel complex inheritance trees in separate auth, schemas, and views modules, and a single view may compose in a half-dozen or more base classes and mixins. This is not ideal for understandability; the MRO gets complex enough that it's often not easy to figure out which method overrides which, or what super calls do.

For auth, many of these problems will be solved by https://github.com/4Catalyzer/flask-resty/issues/236. Outside of auth, though, we need to brainstorm further on how to make these work. One option is to move toward embedding class definitions for functionality. This would then allow patterns like:

class ChildView(ParentView):
    class Authorization(ParentView.Authorization):
        def authorize_delete_item(item):
            return False

    class Schema(ParentView.Schema):
        extra_field = fields.String()

Also, to limit the number of base classes in views, we could factor out classes to handle specific actions. For example, instead of something like:

class WidgetView(VersionedModelMixin, SoftDeleteMixin, GenericModelView):
    version_ignored_columns = ('version_id', 'version_committed_by')

    pass
class WidgetView(GenericModelView):
    class Updater(VersionedUpdater):
        ignored_columns = ('version_id', 'version_committed_by')

    class Deleter(SoftDeleteDeleter):
        pass

This could also enable patterns like:

class WidgetView(GenericModelView):
    class Model(SoftDeleteMixin.Model, db.Model):
        name = sa.Column(sa.String)

    Deleter = SoftDeleteMixin.Deleter

i.e. allow better co-location of mixin-type behavior across models and views.

This likely will require some version of https://github.com/4Catalyzer/flask-resty/issues/237.

taion avatar Feb 18 '19 23:02 taion

not sure the colocation pattern actually gives you more expressiveness.

itajaja avatar Feb 19 '19 13:02 itajaja

It's more like keeping things together. Separately handling "soft delete" or "entity create" mixins on models and views in different modules is annoying and seems potentially error-prone.

taion avatar Feb 19 '19 14:02 taion

Seems like this could be done in a way that doesn't break backwards compat as well.

sloria avatar Feb 19 '19 14:02 sloria

Doesn't solve the multi-inheritance issue, but you could get most of the colocation benefits by organizing modules by resource.

# widgets.py

class WidgetAuthorization(..., Auth):

class WidgetSchema(..., TableSchema):
    class Meta:
        table = Widget.__table__

class BaseWidgetView(... BaseView):

class WidgetListView(BaseWidgetView):

class WidgetView(BaseWidgetView):


api.add_resource('/api/widgets/', WidgetListView, WidgetView)

Update: Removed the model; models aren't 1:1 with resources, so probably still belong in a separate models.py

sloria avatar Aug 29 '19 12:08 sloria

we should definitely try that to at least see how it feels. for big projects I think it might be better

itajaja avatar Aug 29 '19 12:08 itajaja

Yusssssss

taion avatar Aug 29 '19 13:08 taion

I'll write up a quick decision doc in notion and get yall to weigh in

sloria avatar Aug 29 '19 13:08 sloria

The issue FWIW is that cross-module imports in Python suck because exports are values, not bindings. So there will be some awkwardness there.

taion avatar Aug 29 '19 13:08 taion