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

More Decks by Abdur-Rahmaan Janhangeer

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