Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Extending Flask using the Flask Plugins API

Extending Flask using the Flask Plugins API

How to develop Flask plugins

Transcript

  1. About One of the organizer of Python Mauritius Usergroup (pymug)

    / FlaskCon / PyCon Africa OpenSource https://www.compileralchemy.com or Qr 3
  2. encourage people to write extensions give users SUPER powers improve

    Flask dev experience contributes to make Flask a compelling choice saves time 8
  3. Flask app = create_app() | app ---. | V .---------.

    | plugin1 | plugin1.init_app(app) .---------. | app <--. // modified | app ---. | V .---------. | plugin2 | plugin2.init_app(app) .---------. | app <--. // modified | 14
  4. Yes, docs says The most common pattern is to create

    a class that represents the extension’s configuration and behavior, with an init_app method to apply the extension instance to the given application instance. 18
  5. Also The only time the extension should have direct access

    to an app is during init_app, otherwise it should use current_app. 19
  6. class HelloExtension: def __init__(self, app=None): if app is not None:

    self.init_app(app) def init_app(self, app): app.before_request(...) 20
  7. before_request(f) Register a function to run before each request. @app.before_request

    def load_user(): if "user_id" in session: g.user = db.session.get(session["user_id"]) # ... def init_app(self, app): app.before_request(greet_user) def greet_user(self): print('Welcome to FlaskCon') 21
  8. from typing import Any from flask import Blueprint, Flask #

    version is same as tabler-icons __version__ = "3.3.0" class TablerIcons: def __init__(self, app: Any = None) -> None: if app is not None: self.init_app(app) def init_app(self, app: Flask) -> None: if not hasattr(app, "extensions"): app.extensions = {} app.extensions["tabler_icons"] = self bp = Blueprint( "tabler_icons", __name__, static_folder="static", static_url_path=f"/tabler-icons{app.static_url_path}", template_folder="templates", ) app.register_blueprint(bp) app.jinja_env.globals["tabler_icons"] = self app.config.setdefault("TABLER_ICON_SIZE", 24) 31
  9. {% macro fill_css(value="", prefix="", yes_value="", postfix="") %} ... {% endmacro

    %} {% macro render_icon(icon, color="", bold=false, class="", style="", animation="", css_color="", size=0) %} {% if css_color %} {% set css_color = "color: %s;"|format(css_color) %} {% endif %} {% set bold = " font-weight: bold;" if bold else "" %} {% set animation_css = fill_css(animation, ' icon-') %} {% if color or class %} {% set class = "%s %s"|format(class, ("text-" + color) if color else "") %} {% endif %} <span {% if class %}class="{{ class }}"{% endif %} {% if css_color or bold or style %} style="{{ css_color }}{{ bold }} {{ style }}" {% endif %} > <svg class="icon-tabler{{ animation_css }}" width="{{ size or config.TABLER_ICON_SIZE}}" height="{{ size or config.TABLER_ICON_SIZE }}" > <use href="{{ url_for('tabler_icons.static', filename='tabler-sprite.svg') }}#tabler-{{ icon }}" /> </svg> </span> {% endmacro %} 32
  10. from flask import Flask import flask_monitoringdashboard as dashboard app =

    Flask(__name__) @app.route("/test") def test(): return 'ok' dashboard.bind(app) // here app.run() 35
  11. def init_app(self, app: Flask) -> None: if "sqlalchemy" in app.extensions:

    raise RuntimeError( "A 'SQLAlchemy' instance has already been registered on this Flask app." " Import and use that instance instead." ) app.extensions["sqlalchemy"] = self app.teardown_appcontext(self._teardown_session) if self._add_models_to_shell: from .cli import add_models_to_shell app.shell_context_processor(add_models_to_shell) basic_uri: str | sa.engine.URL | None = app.config.setdefault( "SQLALCHEMY_DATABASE_URI", None ) basic_engine_options = self._engine_options.copy() basic_engine_options.update( app.config.setdefault("SQLALCHEMY_ENGINE_OPTIONS", {}) ) echo: bool = app.config.setdefault("SQLALCHEMY_ECHO", False) config_binds: dict[str | None, str | sa.engine.URL | dict[str, t.Any]] = ( app.config.setdefault("SQLALCHEMY_BINDS", {}) ) engine_options: dict[str | None, dict[str, t.Any]] = {} 37
  12. # Build the engine config for each bind key. for

    key, value in config_binds.items(): engine_options[key] = self._engine_options.copy() if isinstance(value, (str, sa.engine.URL)): engine_options[key]["url"] = value else: engine_options[key].update(value) # Build the engine config for the default bind key. if basic_uri is not None: basic_engine_options["url"] = basic_uri if "url" in basic_engine_options: engine_options.setdefault(None, {}).update(basic_engine_options) if not engine_options: raise RuntimeError( "Either 'SQLALCHEMY_DATABASE_URI' or 'SQLALCHEMY_BINDS' must be set." ) 38
  13. engines = self._app_engines.setdefault(app, {}) # Dispose existing engines in case

    init_app is called again. if engines: for engine in engines.values(): engine.dispose() engines.clear() # Create the metadata and engine for each bind key. for key, options in engine_options.items(): self._make_metadata(key) options.setdefault("echo", echo) options.setdefault("echo_pool", echo) self._apply_driver_defaults(options, app) engines[key] = self._make_engine(key, options, app) if app.config.setdefault("SQLALCHEMY_RECORD_QUERIES", False): from . import record_queries for engine in engines.values(): record_queries._listen(engine) if app.config.setdefault("SQLALCHEMY_TRACK_MODIFICATIONS", False): from . import track_modifications track_modifications._listen(self.session) 39