New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pluggy based Plugins #311
Pluggy based Plugins #311
Conversation
from flaskbb.extensions import (db, login_manager, mail, cache, redis_store, | ||
debugtoolbar, alembic, themes, plugin_manager, | ||
babel, csrf, allows, limiter, celery, whooshee) | ||
from flaskbb.extensions import (alembic, allows, babel, cache, celery, csrf, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bit of noise here from when the pluggy instance was created here instead of on the application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it's sorted alphabetically now :)
flaskbb/app.py
Outdated
with app.app_context(): | ||
plugins = PluginRegistry.query.all() | ||
|
||
except OperationalError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is thrown if the table isn't created yet, so upgrades won't bomb. It can potentially be removed later, but I'd need to test a fresh install without it. My gut says the install would fail, but it's been wrong before.
Looks good so far! A few more things that need to be addressed (I'll update this list when I find something):
|
I am using the metadata from Un/installing the fixtures and enabling/disabling plugins should also work via the admin cp although it's not very user friendly at the moment. changing settings still needs to be done. |
flaskbb/plugins/models.py
Outdated
id = db.Column(db.Integer, primary_key=True) | ||
key = db.Column(db.Unicode(255), nullable=False) | ||
value = db.Column(db.PickleType, nullable=False) | ||
# Enum? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, do we go the Enum34 route or just an older SQLA enum (think it's either a list of strings or a space separated string)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd go for Enum34 and change the data type to ChoiceType as we have sqlalchemy-utils
already as dependency.
@@ -619,9 +621,20 @@ def __call__(self, environ, start_response): | |||
|
|||
|
|||
def real(obj): | |||
""" | |||
Unwraps a werkzeug.local.LocalProxy object if given one, else returns the object | |||
"""Unwraps a werkzeug.local.LocalProxy object if given one, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's our line length? My yapf file is set to 100, but that's only because I copied it from work. Should we stick to 79/80?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have always tried to stick to 79/80 but in times with widescreen monitors I think that 100 would make sense :)
@@ -77,7 +77,6 @@ def run_tests(self): | |||
'Flask-Limiter', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If portal is becoming an external package, should we install it by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should install it by default. I have to create a proper package for PyPI first though..
That might be the secret sauce to getting plugin cli commands loaded. However, we'll end up loading the application twice in many instances.
|
Looks like this is the solution. I stumbled upon this the other day and it seems that they are doing something similar. |
I poked around Indico at the behest of ThiefMaster when I was shopping around for plugin solutions, but yes. That looks like the ticket. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more thoughts.
flaskbb/cli/plugins.py
Outdated
) | ||
|
||
# TODO: is there a better way for doing this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, wrap this using with_appcontext
and load the information off of current_app.pluggy
, with a fix I pushed this'll end up loading plugins twice which might cause issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, that shouldn't be needed anymore as we keep track of disabled plugins :) pluggy.list_disabled_plugins()
should provide all information needed for this.
flaskbb/management/views.py
Outdated
|
||
plugin.delete() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wary of this because it only removes the plugin from the blocked list. The next time the application runs, it'll reappear much to the frustration of the user. We should probably include instructions about uninstalling via pip
on this page. We should probably also add an uninstall
hook for plugins to implement so they can do any decoupling needed.
flaskbb/plugins/manager.py
Outdated
for ep in iter_entry_points(entrypoint_name): | ||
# is the plugin registered or blocked? | ||
if self.get_plugin(ep.name) or self.is_blocked(ep.name): | ||
self._disabled_plugins.append((ep.name, ep.dist)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only append here if the plugin is actually disabled, not merely already loaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, yeah you are right. Thanks for pointing it out :)
flaskbb/plugins/models.py
Outdated
value = db.Column(db.PickleType, nullable=False) | ||
# Enum? | ||
# Available types: string, integer, float, boolean, select, selectmultiple | ||
value_type = db.Column(db.Unicode(20), nullable=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed these extra nullable fields break the association_proxy
on PluginRegistry. Should we keep that or remove it? At least it breaks for setting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mhh thats true.. what do you suggest? We could also revamp the way we create the setting forms but thats probably a different issue :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mm, not sure. I like the idea of a simple KV store but if we need more information that what we can get out of that, it's not worth trying to make it work. I'm tempted to say we could infer the value's type, but that could lead to issues.
flaskbb/utils/translations.py
Outdated
@@ -38,7 +35,7 @@ def __init__(self, app): | |||
with self.app.app_context(): | |||
self.plugin_translations = [ | |||
os.path.join(plugin.path, "translations") | |||
for plugin in get_enabled_plugins() | |||
for plugin in self.app.pluggy.get_plugins() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we did self.plugin_translations = self.app.pluggy.load_translations()
and have the expectation be the plugin returns a list of translation paths (or maybe a dictionary of language -> path/List[path]). That's all we'd need to complete the translation compilation, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I just blindly changed it to make flaskbb runable again.. I really should stop doing that :P
I like the idea - I'm not entirely familiar with the inner workings of click but I think all the subcommands would need the same signature ie can't mix This would leave you with something like
which would at least appear under On the other hand, it might be an idea not to bombard the end user with all these options, after all most of the db commands are involved in generating migration files rather than applying them - other than |
@@ -8,7 +8,7 @@ html { | |||
body { | |||
/* Margin bottom by footer height */ | |||
margin-bottom: 60px; | |||
padding-top: 50px; | |||
/*padding-top: 50px; */ /* uncomment if navbar should be fixed */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed as in "position: fixed"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if navbar-fixed-top
is used
docs/hooks.rst
Outdated
|
||
.. currentmodule:: flaskbb.plugins.spec | ||
|
||
.. autofunction:: flaskbb_extensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably give more details about when these are called. I'll knock out some docs on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll wait for the merge then.
Each hook specification has a corresponding hook implementation. By default, | ||
all hooks that are prefix with ``flaskbb_`` will be marked as a standard | ||
hook implementation. It is possible to modify the behavior of hooks. | ||
For example, default hooks are called in LIFO registered order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should clarify that registration order isn't deterministic -- unless I misunderstood something in the pluggy docs or there's something in setuptools that guarantees that.
Wooo, all of this looks good. I'll knock out the changes to the docs above and handle this conflict we have. |
# Conflicts: # flaskbb/configs/default.py
Addresses #183
WIP PR for adding pluggy based plugins to FlaskBB.
Currently this includes:
I'm having some issues with loading CLI commands from plugins because the Flask-Click bridge only creates an application when absolutely needed, however plugins are loaded on application creation, so even with overriding the
_load_plugin_commands
hook in the FlaskGroup doesn't work as the FlaskBB instance hasn't been created yet.Next steps here are: