File: cli.py

package info (click to toggle)
python-flask-seeder 1.2.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 292 kB
  • sloc: python: 1,062; makefile: 2
file content (192 lines) | stat: -rwxr-xr-x 5,364 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
""" Module for seed cli subgroup

$ FLASK_APP=app flask seed <command>
"""

import os
import re
import importlib.util
import inspect
from itertools import groupby, chain

import click
from flask.cli import with_appcontext
from flask import current_app as app

from flask_seeder import Seeder

def get_seed_scripts(root="seeds"):
    """ Get seed scripts

    Recursively walk a directory structure and find all python scripts.

    Arguments:
        root: Optional root directory to start walking (Default: "seeds")

    Returns:
        Returns a list with python file paths to found python scripts.
        If no files are found, and empty list is returned.

    """
    extension = "py"
    result = []
    for path, _, files in os.walk(root):
        for filename in files:
            if not re.search(r"\."+extension+"$", filename):
                continue

            # Concat the full file path
            file_path = os.path.join(path, filename)

            result.append(file_path)

    return result

def inheritsfrom(child, parent):
    """ Verify inheritance

    Check if child inherits from parent. Does the same thing as built-in
    issubclass() except child and parent can't be the same class.

    Arguments:
        child: Child class
        parent: Parent class

    Returns:
        Returns True if Child inherits from Parent.
        Returns False if Child does not inherit from Parent or if Child and Parent
        is the same class.
    """
    return issubclass(child, parent) and child != parent

def load_seeder(cls, name, mod_path, file_path):
    """ Load seeder instance

    Instantiate a seeder using common base settings.

    Arguments:
        cls: Seeder class
        name: String name representation of the seeder
        mod_path: Python module path (package.module)
        file_path: Path to the python file in the file system

    Returns:
        Returns an instance of `cls`.
    """
    seeder = cls()
    seeder.name = name
    seeder.mod_path = mod_path
    seeder.file_path = file_path

    return seeder

def get_seeders_from_script(script):
    """ Get all seeders from a script file

    Reads a python script and detecs all classes within that inherits from the base Seeder class.

    Arguments:
        script: Filesystem path to the script

    Returns:
        Returns a list of loaded seeder objects from the script
    """
    seeders = []
    spec = importlib.util.spec_from_file_location("flask_seeder.seeder.ext", script)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    members = inspect.getmembers(module, inspect.isclass)
    for member in members:
        if inheritsfrom(member[1], Seeder):
            seeder = load_seeder(member[1], member[0], "", script)
            seeders.append(seeder)

    return seeders

def get_seeders(root=None):
    """ Get all seeders from all scripts

    Finds all python scripts with seeders, loads them and return them.

    Returns:
        Ordered list of loaded seeder objects based on priority and class name
    """
    seeders = []
    if root is not None:
        scripts = get_seed_scripts(root=root)
    else:
        scripts = get_seed_scripts()

    for script in scripts:
        seeders.extend(get_seeders_from_script(script))

    priority_key = lambda s: getattr(s, "priority", float("inf"))
    sorted_seeders = (
        sorted(g, key=lambda s: type(s).__name__)
        for k, g in groupby(sorted(seeders, key=priority_key), key=priority_key)
    )

    return chain.from_iterable(sorted_seeders)


@click.group()
def seed():
    """ Database seed commands """

@seed.command("run")
@click.option("--root", default="seeds", type=click.Path(),
              help="Root directory for seed scripts",
              envvar="FLASK_SEEDER_ROOT")
@click.option("--commit/--no-commit", default=True,
              help="Commit changes to database after seeding",
              envvar="FLASK_SEEDER_AUTOCOMMIT")
@click.argument("seeders", nargs=-1)
@with_appcontext
def seed_run(root, commit, seeders):
    """ Run database seeders

    Any optional arguments after the options will be treated as a list of seeders to run,
    for example:

        $ flask seed run DemoSeeder AnotherDemoSeeder

    """
    click.echo("Running database seeders")
    db = None
    try:
        db = app.extensions["flask_seeder"].db
    except KeyError:
        raise RuntimeError("Flask-Seeder not initialized!")

    for seeder in get_seeders(root=root):
        if seeders and seeder.name not in seeders:
            continue

        seeder.db = db
        try:
            seeder.run()
        # pylint: disable=broad-except,invalid-name
        except Exception as e:
            click.echo("%s...\t[ERROR]" % seeder.name)
            click.echo("\t%s" % e)
            continue

        click.echo("%s...\t[OK]" % seeder.name)

    if not commit:
        click.echo("Not committing changes to database!")
        return

    click.echo("Committing to database!")
    db.session.commit()


@seed.command("list")
@click.option("--root", default="seeds", type=click.Path(),
              help="Root directory for seed scripts",
              envvar="FLASK_SEEDER_ROOT")
def seed_list(root):
    """ List all discoverable seeders """
    for seeder in get_seeders(root=root):
        click.echo("* %s" % seeder.name)