Why do we use entry points for server side extensions?

If you want to extend the platform and have it run your own plugin’s classes, the way to do it today is to define the appropriate entry point to your package’s setup.py so that it gets run when your package is installed. The edx-platform repo itself sets a whole bunch of these:

One annoyance that this mechanism has is that any change in your class name or path requires re-running setup.py, and simple code refactors of the bundled extensions (e.g. XBlocks in edx-platform) can break everyone’s dev environment.

This actually makes sense to me for extension mechanisms that might have to work outside of Django (like opaque-keys). But almost all of our extension points are in Django, and Django already has its own conventions and library support for specifying modules in settings files. Django itself uses it to specify apps, middleware, template loaders, and all kinds of other things.

The biggest difference that I can think of is when it comes to bundling multiple extension points together–e.g. you have a repo that implements a handful of XBlocks, a course tab, and a partition scheme. In that kind of scenario, using entry points lets you install it all at once without having to worry that you missed specifying one of the pieces in a settings file. On the other hand, the way we do it now forces you to install everything–there’s no way to selectively pick a few pieces.

I’m not pushing for changing how we do this yet, but I would be interested in any feedback folks have on entry points vs. Django-style imports, and what tradeoffs you’ve encountered.

There are some notes in edx-django-utils/README.rst at master · openedx/edx-django-utils · GitHub on why we chose to use entry points for Django app plugins. One of the key points is essentially “I shouldn’t have to update settings files, URL mappings, etc. in edx-platform just because I installed this extra functionality…especially if it’s optional.” To your point, the workflow can be problematic when we have lots of these defined in the service itself, where we have to explicitly re-run setup.py instead of it happening organically when upgrading a dependency. But I’m not sure if there’s a good way to work around that if we want both extension points usable by external packages and a set of stock components of that type which come out of the box in edx-platform itself.

Another issue with extension points is that you can’t control the order in which they load. Now, having plugins which depend on a specific order may be a bad sign, but certainly the django approach does let you control the order if you ever need to.

Isn’t this the same if you have the full path to the plugin’s class listed for some setting in settings.py (the django way) ?

Today we have tutor plugins which provide a similar experience even for things that do need configuring. Instead of installing X directly, you install tutor-contrib-x and it should “just work”. Though I know not everyone uses Tutor, nor do plugins exist for every possible extension. It is certainly possible to bundle a tiny Tutor plugin with whatever extension you have though.

Maybe it’s too narrow a use case, but I think about all the broken dev environments from when we did XModule → XBlock name refactoring. If that mapping had been in common.py, it would have just worked out of the box instead of requiring people re-run setup.py to get the new class mappings.

I see. Though it’s not that unusual for some code change to require users to install requirements or run migrations or similar before the devstack will work again, so I don’t see that as fundamentally different issue introduced by entry points. But you’re right that it would avoid some extra work or confusion in some cases.

A very niche use case is where you want to use most of a configuration mapping but override a tiny bit in your local instance. For instance, one idea I had floated recently was creating an alternative XBlock for video that would use many of the same fields and could be mapped in as an alternative to VideoBlock (so the xblock.v1 mapping for the “video” tag would go to it).