fix(migrations): Fix rebase issues
This commit is contained in:
parent
6c1a0a01f4
commit
8fbdac365f
|
@ -1,4 +1,5 @@
|
||||||
# A generic, single database configuration.
|
# A generic, single database configuration.
|
||||||
|
# No used by flaschengeist
|
||||||
|
|
||||||
[alembic]
|
[alembic]
|
||||||
# template used to generate migration files
|
# template used to generate migration files
|
||||||
|
@ -9,7 +10,7 @@
|
||||||
# revision_environment = false
|
# revision_environment = false
|
||||||
|
|
||||||
version_path_separator = os
|
version_path_separator = os
|
||||||
version_locations = %(here)s/versions
|
version_locations = %(here)s/migrations
|
||||||
|
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
[loggers]
|
[loggers]
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
from pathlib import Path
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ config = context.config
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
# Interpret the config file for Python logging.
|
||||||
# This line sets up loggers basically.
|
# This line sets up loggers basically.
|
||||||
fileConfig(config.config_file_name)
|
fileConfig(Path(config.get_main_option("script_location")) / config.config_file_name.split("/")[-1])
|
||||||
logger = logging.getLogger("alembic.env")
|
logger = logging.getLogger("alembic.env")
|
||||||
|
|
||||||
config.set_main_option("sqlalchemy.url", str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"))
|
config.set_main_option("sqlalchemy.url", str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"))
|
|
@ -1,8 +1,8 @@
|
||||||
"""Initial migration.
|
"""Flaschengeist: Initial
|
||||||
|
|
||||||
Revision ID: d3026757c7cb
|
Revision ID: 255b93b6beed
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2021-12-19 20:34:34.122576
|
Create Date: 2022-02-23 14:33:02.851388
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
@ -11,9 +11,9 @@ import flaschengeist
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "d3026757c7cb"
|
revision = "255b93b6beed"
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = ("flaschengeist",)
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,14 +35,6 @@ def upgrade():
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_permission")),
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_permission")),
|
||||||
sa.UniqueConstraint("name", name=op.f("uq_permission_name")),
|
sa.UniqueConstraint("name", name=op.f("uq_permission_name")),
|
||||||
)
|
)
|
||||||
op.create_table(
|
|
||||||
"plugin_setting",
|
|
||||||
sa.Column("id", flaschengeist.models.Serial(), nullable=False),
|
|
||||||
sa.Column("plugin", sa.String(length=30), nullable=True),
|
|
||||||
sa.Column("name", sa.String(length=30), nullable=False),
|
|
||||||
sa.Column("value", sa.PickleType(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_plugin_setting")),
|
|
||||||
)
|
|
||||||
op.create_table(
|
op.create_table(
|
||||||
"role",
|
"role",
|
||||||
sa.Column("id", flaschengeist.models.Serial(), nullable=False),
|
sa.Column("id", flaschengeist.models.Serial(), nullable=False),
|
||||||
|
@ -135,7 +127,6 @@ def downgrade():
|
||||||
op.drop_table("user")
|
op.drop_table("user")
|
||||||
op.drop_table("role_x_permission")
|
op.drop_table("role_x_permission")
|
||||||
op.drop_table("role")
|
op.drop_table("role")
|
||||||
op.drop_table("plugin_setting")
|
|
||||||
op.drop_table("permission")
|
op.drop_table("permission")
|
||||||
op.drop_table("image")
|
op.drop_table("image")
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
|
@ -84,7 +84,7 @@ def cli():
|
||||||
|
|
||||||
|
|
||||||
def main(*args, **kwargs):
|
def main(*args, **kwargs):
|
||||||
# from .plugin_cmd import plugin
|
from .plugin_cmd import plugin
|
||||||
from .export_cmd import export
|
from .export_cmd import export
|
||||||
from .docs_cmd import docs
|
from .docs_cmd import docs
|
||||||
from .run_cmd import run
|
from .run_cmd import run
|
||||||
|
@ -92,8 +92,8 @@ def main(*args, **kwargs):
|
||||||
# Override logging level
|
# Override logging level
|
||||||
environ.setdefault("FG_LOGGING", logging.getLevelName(LOGGING_MAX))
|
environ.setdefault("FG_LOGGING", logging.getLevelName(LOGGING_MAX))
|
||||||
|
|
||||||
# cli.add_command(plugin)
|
|
||||||
cli.add_command(export)
|
cli.add_command(export)
|
||||||
cli.add_command(docs)
|
cli.add_command(docs)
|
||||||
|
cli.add_command(plugin)
|
||||||
cli.add_command(run)
|
cli.add_command(run)
|
||||||
cli(*args, **kwargs)
|
cli(*args, **kwargs)
|
||||||
|
|
|
@ -2,7 +2,9 @@ import click
|
||||||
from click.decorators import pass_context
|
from click.decorators import pass_context
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
from flaschengeist.utils.plugin import get_plugins, plugin_version
|
from importlib_metadata import EntryPoint, entry_points
|
||||||
|
|
||||||
|
from flaschengeist.config import config
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
|
@ -67,17 +69,28 @@ def uninstall(ctx: click.Context, plugin):
|
||||||
|
|
||||||
@plugin.command()
|
@plugin.command()
|
||||||
@click.option("--enabled", "-e", help="List only enabled plugins", is_flag=True)
|
@click.option("--enabled", "-e", help="List only enabled plugins", is_flag=True)
|
||||||
|
@click.option("--no-header", "-n", help="Do not show header", is_flag=True)
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
def ls(enabled):
|
def ls(enabled, no_header):
|
||||||
if enabled:
|
def plugin_version(p):
|
||||||
plugins = current_app.config["FG_PLUGINS"].values()
|
if isinstance(p, EntryPoint):
|
||||||
else:
|
return p.dist.version
|
||||||
plugins = get_plugins()
|
return p.version
|
||||||
|
|
||||||
|
plugins = entry_points(group="flaschengeist.plugins")
|
||||||
|
enabled_plugins = [key for key, value in config.items() if "enabled" in value] + [config["FLASCHENGEIST"]["auth"]]
|
||||||
|
loaded_plugins = current_app.config["FG_PLUGINS"].keys()
|
||||||
|
|
||||||
|
if not no_header:
|
||||||
print(f"{' '*13}{'name': <20}|{'version': >10}")
|
print(f"{' '*13}{'name': <20}|{'version': >10}")
|
||||||
print("-" * 46)
|
print("-" * 46)
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
|
if enabled and plugin.name not in enabled_plugins:
|
||||||
|
continue
|
||||||
print(
|
print(
|
||||||
f"{plugin.id: <33}|{plugin_version(plugin): >12}"
|
f"{plugin.name: <33}|{plugin_version(plugin): >12}"
|
||||||
f"{click.style(' (enabled)', fg='green') if plugin.id in current_app.config['FG_PLUGINS'] else click.style(' (disabled)', fg='red')}"
|
f"{click.style(' (enabled)', fg='green') if plugin.name in enabled_plugins else ' (disabled)'}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for plugin in [value for value in enabled_plugins if value not in loaded_plugins]:
|
||||||
|
print(f"{plugin: <33}|{' '*12}" f"{click.style(' (not found)', fg='red')}")
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import os
|
import os
|
||||||
from flask import current_app
|
from flask_migrate import Migrate, Config
|
||||||
from flask_migrate import Migrate
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from importlib_metadata import EntryPoint
|
||||||
from sqlalchemy import MetaData
|
from sqlalchemy import MetaData
|
||||||
|
|
||||||
|
from flaschengeist import logger
|
||||||
|
|
||||||
# https://alembic.sqlalchemy.org/en/latest/naming.html
|
# https://alembic.sqlalchemy.org/en/latest/naming.html
|
||||||
metadata = MetaData(
|
metadata = MetaData(
|
||||||
naming_convention={
|
naming_convention={
|
||||||
|
@ -21,31 +23,40 @@ migrate = Migrate()
|
||||||
|
|
||||||
|
|
||||||
@migrate.configure
|
@migrate.configure
|
||||||
def configure_alembic(config):
|
def configure_alembic(config: Config):
|
||||||
"""Alembic configuration hook
|
"""Alembic configuration hook
|
||||||
|
|
||||||
Inject all migrations paths into the ``version_locations`` config option.
|
Inject all migrations paths into the ``version_locations`` config option.
|
||||||
This includes even disabled plugins, as simply disabling a plugin without
|
This includes even disabled plugins, as simply disabling a plugin without
|
||||||
uninstall can break the alembic version management.
|
uninstall can break the alembic version management.
|
||||||
"""
|
"""
|
||||||
import inspect, pathlib
|
from importlib_metadata import entry_points, distribution
|
||||||
from flaschengeist.utils.plugin import get_plugins
|
|
||||||
|
# Set main script location
|
||||||
|
config.set_main_option(
|
||||||
|
"script_location", str(distribution("flaschengeist").locate_file("") / "flaschengeist" / "alembic")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set Flaschengeist's migrations
|
||||||
|
migrations = [config.get_main_option("script_location") + "/migrations"]
|
||||||
|
|
||||||
|
# Gather all migration paths
|
||||||
|
ep: EntryPoint
|
||||||
|
for ep in entry_points(group="flaschengeist.plugins"):
|
||||||
|
try:
|
||||||
|
directory = ep.dist.locate_file("")
|
||||||
|
for loc in ep.module.split(".") + ["migrations"]:
|
||||||
|
directory /= loc
|
||||||
|
if directory.exists():
|
||||||
|
logger.debug(f"Adding migration version path {directory}")
|
||||||
|
migrations.append(str(directory.resolve()))
|
||||||
|
except:
|
||||||
|
logger.warning(f"Could not load migrations of plugin {ep.name} for database migration.")
|
||||||
|
logger.debug("Plugin loading failed", exc_info=True)
|
||||||
|
|
||||||
# Load migration paths from plugins
|
|
||||||
migrations = [(pathlib.Path(inspect.getfile(p)).parent / "migrations") for p in get_plugins()]
|
|
||||||
migrations = [str(m.resolve()) for m in migrations if m.exists()]
|
|
||||||
if len(migrations) > 0:
|
|
||||||
# Get configured paths
|
|
||||||
paths = config.get_main_option("version_locations")
|
|
||||||
# Get configured path seperator
|
|
||||||
sep = config.get_main_option("version_path_separator", "os")
|
|
||||||
if paths:
|
|
||||||
# Insert configured paths at the front, before plugin migrations
|
|
||||||
migrations.insert(0, config.get_main_option("version_locations"))
|
|
||||||
sep = os.pathsep if sep == "os" else " " if sep == "space" else sep
|
|
||||||
# write back seperator (we changed it if neither seperator nor locations were specified)
|
# write back seperator (we changed it if neither seperator nor locations were specified)
|
||||||
config.set_main_option("version_path_separator", sep)
|
config.set_main_option("version_path_separator", os.pathsep)
|
||||||
config.set_main_option("version_locations", sep.join(migrations))
|
config.set_main_option("version_locations", os.pathsep.join(set(migrations)))
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
from importlib_metadata import Distribution, EntryPoint
|
from importlib_metadata import Distribution, EntryPoint
|
||||||
from werkzeug.exceptions import MethodNotAllowed, NotFound
|
from werkzeug.exceptions import MethodNotAllowed, NotFound
|
||||||
from werkzeug.datastructures import FileStorage
|
from werkzeug.datastructures import FileStorage
|
||||||
|
@ -102,8 +103,16 @@ class Plugin:
|
||||||
models = None
|
models = None
|
||||||
"""Optional module containing the SQLAlchemy models used by the plugin"""
|
"""Optional module containing the SQLAlchemy models used by the plugin"""
|
||||||
|
|
||||||
migrations_path = None
|
migrations: Optional[tuple[str, str]] = None
|
||||||
"""Optional location of the path to migration files, required if custome db tables are used"""
|
"""Optional identifiers of the migration versions
|
||||||
|
|
||||||
|
If custom database tables are used migrations must be provided and the
|
||||||
|
head and removal versions had to be defined, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
migrations = ("head_hash", "removal_hash")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, entry_point: EntryPoint, config=None):
|
def __init__(self, entry_point: EntryPoint, config=None):
|
||||||
"""Constructor called by create_app
|
"""Constructor called by create_app
|
||||||
|
@ -145,7 +154,7 @@ class Plugin:
|
||||||
"""
|
"""
|
||||||
from ..controller import pluginController
|
from ..controller import pluginController
|
||||||
|
|
||||||
return pluginController.get_setting(self.id, name, **kwargs)
|
return pluginController.get_setting(self.name, name, **kwargs)
|
||||||
|
|
||||||
def set_setting(self, name: str, value):
|
def set_setting(self, name: str, value):
|
||||||
"""Save setting in database
|
"""Save setting in database
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
Extends users plugin with balance functions
|
Extends users plugin with balance functions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pathlib
|
from flask import current_app
|
||||||
from flask import Blueprint, current_app
|
|
||||||
from werkzeug.local import LocalProxy
|
from werkzeug.local import LocalProxy
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
@ -58,8 +57,10 @@ def service_debit():
|
||||||
|
|
||||||
class BalancePlugin(Plugin):
|
class BalancePlugin(Plugin):
|
||||||
permissions = permissions.permissions
|
permissions = permissions.permissions
|
||||||
plugin: "BalancePlugin" = LocalProxy(lambda: current_app.config["FG_PLUGINS"][BalancePlugin.name])
|
|
||||||
models = models
|
models = models
|
||||||
|
migrations = True
|
||||||
|
|
||||||
|
plugin: "BalancePlugin" = LocalProxy(lambda: current_app.config["FG_PLUGINS"][BalancePlugin.name])
|
||||||
|
|
||||||
def __init__(self, entry_point, config):
|
def __init__(self, entry_point, config):
|
||||||
super(BalancePlugin, self).__init__(entry_point, config)
|
super(BalancePlugin, self).__init__(entry_point, config)
|
||||||
|
@ -67,8 +68,6 @@ class BalancePlugin(Plugin):
|
||||||
|
|
||||||
self.blueprint = blueprint
|
self.blueprint = blueprint
|
||||||
|
|
||||||
self.migrations_path = (pathlib.Path(__file__).parent / "migrations").resolve()
|
|
||||||
|
|
||||||
@plugins_loaded
|
@plugins_loaded
|
||||||
def post_loaded(*args, **kwargs):
|
def post_loaded(*args, **kwargs):
|
||||||
if config.get("allow_service_debit", False) and "events" in current_app.config["FG_PLUGINS"]:
|
if config.get("allow_service_debit", False) and "events" in current_app.config["FG_PLUGINS"]:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""Initial balance migration
|
"""balance: initial
|
||||||
|
|
||||||
Revision ID: f07df84f7a95
|
Revision ID: 98f2733bbe45
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2021-12-19 21:12:53.192267
|
Create Date: 2022-02-23 14:41:03.089145
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
@ -11,10 +11,10 @@ import flaschengeist
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "f07df84f7a95"
|
revision = "98f2733bbe45"
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = ("balance",)
|
branch_labels = ("balance",)
|
||||||
depends_on = "d3026757c7cb"
|
depends_on = "flaschengeist"
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
|
@ -1,8 +1,8 @@
|
||||||
"""Initial pricelist migration
|
"""pricelist: initial
|
||||||
|
|
||||||
Revision ID: 7d9d306be676
|
Revision ID: 58ab9b6a8839
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2021-12-19 21:43:30.203811
|
Create Date: 2022-02-23 14:45:30.563647
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
@ -11,10 +11,10 @@ import flaschengeist
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "7d9d306be676"
|
revision = "58ab9b6a8839"
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = ("pricelist",)
|
branch_labels = ("pricelist",)
|
||||||
depends_on = "d3026757c7cb"
|
depends_on = "flaschengeist"
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
|
@ -1,49 +0,0 @@
|
||||||
"""Plugin utils
|
|
||||||
|
|
||||||
Utilities for handling Flaschengeist plugins
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
from flaschengeist import logger
|
|
||||||
from flaschengeist.plugins import Plugin
|
|
||||||
|
|
||||||
|
|
||||||
def get_plugins() -> list[type[Plugin]]:
|
|
||||||
"""Get all installed plugins for Flaschengeist
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list of classes implementing `flaschengeist.plugins.Plugin`
|
|
||||||
"""
|
|
||||||
logger.debug("Search for plugins")
|
|
||||||
|
|
||||||
plugins = []
|
|
||||||
for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugins"):
|
|
||||||
try:
|
|
||||||
logger.debug(f"Found plugin: >{entry_point.name}<")
|
|
||||||
plugin_class = entry_point.load()
|
|
||||||
if issubclass(plugin_class, Plugin):
|
|
||||||
plugins.append(plugin_class)
|
|
||||||
except TypeError:
|
|
||||||
logger.error(f"Invalid entry point for plugin {entry_point.name} found.")
|
|
||||||
except pkg_resources.DistributionNotFound:
|
|
||||||
logger.warn(f"Requirements not fulfilled for {entry_point.name}")
|
|
||||||
logger.debug("DistributionNotFound", exc_info=True)
|
|
||||||
return plugins
|
|
||||||
|
|
||||||
|
|
||||||
def plugin_version(plugin: type[Plugin]) -> str:
|
|
||||||
"""Get version of plugin
|
|
||||||
|
|
||||||
Returns the version of a plugin, if plugin does not set the
|
|
||||||
version property, the version of the package providing the
|
|
||||||
plugin is taken.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plugin: Plugin or Plugin class
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Version as string
|
|
||||||
"""
|
|
||||||
if plugin.version:
|
|
||||||
return plugin.version
|
|
||||||
return pkg_resources.get_distribution(plugin.__module__.split(".", 1)[0]).version
|
|
Loading…
Reference in New Issue