Skip to content

Commit

Permalink
Define hooks, loader and registry
Browse files Browse the repository at this point in the history
  • Loading branch information
justanr committed Aug 13, 2017
1 parent cfc7d9a commit 0511196
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 5 deletions.
52 changes: 47 additions & 5 deletions flaskbb/app.py
Expand Up @@ -15,6 +15,7 @@

from sqlalchemy import event
from sqlalchemy.engine import Engine
from sqlalchemy.exc import OperationalError
from flask import Flask, request
from flask_login import current_user

Expand All @@ -28,9 +29,10 @@
# models
from flaskbb.user.models import User, Guest
# extensions
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,
db, debugtoolbar, limiter, login_manager, mail,
plugin_manager, redis_store, themes, whooshee)

# various helpers
from flaskbb.utils.helpers import (time_utcnow, format_date, time_since,
crop_title, is_online, mark_online,
Expand All @@ -50,6 +52,11 @@
# app specific configurations
from flaskbb.utils.settings import flaskbb_config

from flaskbb.plugins.models import PluginRegistry
from flaskbb.plugins import spec

from pluggy import PluginManager


def create_app(config=None):
"""Creates the app.
Expand All @@ -61,16 +68,17 @@ def create_app(config=None):
later overwrite it from the ENVVAR.
"""
app = Flask("flaskbb")

configure_app(app, config)
configure_celery_app(app, celery)
configure_blueprints(app)
configure_extensions(app)
load_plugins(app)
configure_blueprints(app)
configure_template_filters(app)
configure_context_processors(app)
configure_before_handlers(app)
configure_errorhandlers(app)
configure_logging(app)
app.pluggy.hook.flaskbb_additional_setup(app=app, pluggy=app.pluggy)

return app

Expand All @@ -96,6 +104,7 @@ def configure_app(app, config):
# Parse the env for FLASKBB_ prefixed env variables and set
# them on the config object
app_config_from_env(app, prefix="FLASKBB_")
app.pluggy = PluginManager('flaskbb', implprefix='flaskbb_')


def configure_celery_app(app, celery):
Expand Down Expand Up @@ -125,6 +134,8 @@ def configure_blueprints(app):
message, url_prefix=app.config["MESSAGE_URL_PREFIX"]
)

app.pluggy.hook.flaskbb_load_blueprints(app=app)


def configure_extensions(app):
"""Configures the extensions."""
Expand Down Expand Up @@ -239,6 +250,8 @@ def configure_template_filters(app):

app.jinja_env.filters.update(filters)

app.pluggy.hook.flaskbb_jinja_directives(app=app)


def configure_context_processors(app):
"""Configures the context processors."""
Expand Down Expand Up @@ -273,6 +286,8 @@ def mark_current_user_online():
else:
mark_online(request.remote_addr, guest=True)

app.pluggy.hook.flaskbb_request_processors(app=app)


def configure_errorhandlers(app):
"""Configures the error handlers."""
Expand All @@ -289,6 +304,8 @@ def page_not_found(error):
def server_error_page(error):
return render_template("errors/server_error.html"), 500

app.pluggy.hook.flaskbb_errorhandlers(app=app)


def configure_logging(app):
"""Configures logging."""
Expand Down Expand Up @@ -349,3 +366,28 @@ def after_cursor_execute(conn, cursor, statement,
parameters, context, executemany):
total = time.time() - conn.info['query_start_time'].pop(-1)
app.logger.debug("Total Time: %f", total)


def load_plugins(app):
app.pluggy.add_hookspecs(spec)
try:
with app.app_context():
plugins = PluginRegistry.query.all()

except OperationalError:
return

for plugin in plugins:
if not plugin.enabled:
app.pluggy.set_blocked(plugin.name)

app.pluggy.load_setuptools_entrypoints('flaskbb_plugin')
app.pluggy.hook.flaskbb_extensions(app=app)

loaded_names = set([p[0] for p in app.pluggy.list_name_plugin()])
registered_names = set([p.name for p in plugins])
unregistered = [PluginRegistry(name=name) for name in loaded_names - registered_names]

with app.app_context():
db.session.add_all(unregistered)
db.session.commit()
47 changes: 47 additions & 0 deletions flaskbb/plugins/models.py
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""
flaskbb.plugins.models
~~~~~~~~~~~~~~~~~~~~~~~
This module provides registration and a basic DB backed key-value store for plugins.
:copyright: (c) 2017 by the FlaskBB Team.
:license: BSD, see LICENSE for more details.
"""
from sqlalchemy import UniqueConstraint
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm.collections import attribute_mapped_collection

from flaskbb.extensions import db


class PluginStore(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(255), nullable=False)
value = db.Column(db.Unicode(255))
plugin_id = db.Column(db.Integer, db.ForeignKey('plugin_registry.id'))

__table_args__ = (UniqueConstraint('name', 'plugin_id', name='plugin_kv_uniq'), )

def __repr__(self):
return '<FlaskBBPluginSetting plugin={} name={} value={}>'.format(
self.plugin.name, self.name, self.value
)


class PluginRegistry(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(255), unique=True, nullable=False)
enabled = db.Column(db.Boolean, default=True)
values = db.relationship(
'PluginStore',
collection_class=attribute_mapped_collection('name'),
cascade='all, delete-orphan',
backref='plugin'
)
settings = association_proxy(
'values', 'value', creator=lambda k, v: PluginStore(name=k, value=v)
)

def __repr__(self):
return '<FlaskBBPlugin name={} enabled={}>'.format(self.name, self.id)
64 changes: 64 additions & 0 deletions flaskbb/plugins/spec.py
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
flaskbb.plugins.spec
~~~~~~~~~~~~~~~~~~~~~~~
This module provides the core FlaskBB plugin hook definitions
:copyright: (c) 2017 by the FlaskBB Team.
:license: BSD, see LICENSE for more details.
"""

from pluggy import HookspecMarker

spec = HookspecMarker('flaskbb')


@spec
def flaskbb_extensions(app):
"""
Hook for initializing any plugin loaded extensions
"""


@spec
def flaskbb_load_blueprints(app):
"""
Hook for registering blueprints
"""


@spec
def flaskbb_request_processors(app):
"""
Hook for registering pre/post request processors
"""


@spec
def flaskbb_errorhandlers(app):
"""
Hook for registering error handlers
"""


@spec
def flaskbb_jinja_directives(app):
"""
Hook for registering jinja filters, context processors, etc
"""


@spec
def flaskbb_additional_setup(app, pluggy):
"""
Hook for any additional setup a plugin wants to do after all other application
setup has finished
"""


@spec
def flaskbb_cli(cli):
"""
Hook for registering CLI commands
"""
44 changes: 44 additions & 0 deletions migrations/7c3fcf8a3335_add_plugin_tables.py
@@ -0,0 +1,44 @@
"""Add plugin tables
Revision ID: 7c3fcf8a3335
Revises:
Create Date: 2017-08-12 12:41:04.725309
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '7c3fcf8a3335'
down_revision = None
branch_labels = ('default',)
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('plugin_registry',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.Unicode(length=255), nullable=False),
sa.Column('enabled', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('plugin_store',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.Unicode(length=255), nullable=False),
sa.Column('value', sa.Unicode(length=255), nullable=True),
sa.Column('plugin_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['plugin_id'], ['plugin_registry.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name', 'plugin_id', name='plugin_kv_uniq')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('plugin_store')
op.drop_table('plugin_registry')
# ### end Alembic commands ###

0 comments on commit 0511196

Please sign in to comment.