diff --git a/pyproject.toml b/pyproject.toml index 7e8d1ea..1d88687 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ python-dotenv = "*" rich = "*" typer = "*" flask = "*" +flask-sqlalchemy = "^3.1.1" +flask-migrate = "^4.1.0" [tool.poetry.group.dev.dependencies] pytest = "*" @@ -25,7 +27,8 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -ttfrog = "ttfrog.cli:app" +ttfrog = "ttfrog.cli:main" +web = "ttfrog.web:main" ### SLAM diff --git a/src/ttfrog/app.py b/src/ttfrog/app.py new file mode 100644 index 0000000..be8d347 --- /dev/null +++ b/src/ttfrog/app.py @@ -0,0 +1,50 @@ +import importlib +import os + +from flask import Flask +from flask_migrate import Migrate +from flask_sqlalchemy import SQLAlchemy + +_context = None + + +class ApplicationContext: + """ + Interface for the flask application + """ + + def __init__(self, schema_module_name: str): + self.schema_module = importlib.import_module(schema_module_name) + self.db = SQLAlchemy() + self.migrate = Migrate() + self._app = Flask("flask") + + self._initialized = False + self._enable_migrations = False + + self._app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", "secret string") + self._app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv( + "DATABASE_URL", "sqlite:////" + os.path.join(self._app.root_path, "data.db") + ) + self._app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + + @property + def flask(self): + if not self._initialized: + raise RuntimeError("You must initialize first.") + return self._app + + def initialize(self): + self.db.init_app(self._app) + if self._enable_migrations: + self.migrate.init_app(self._app, self.db) + self._initialized = True + return self.flask + + +def initialize(schema_module_name: str = "ttfrog.schema"): + global _context + if not _context: + _context = ApplicationContext(schema_module_name) + _context.initialize() + return _context diff --git a/src/ttfrog/cli.py b/src/ttfrog/cli.py index 954c0c4..91455fa 100644 --- a/src/ttfrog/cli.py +++ b/src/ttfrog/cli.py @@ -3,17 +3,25 @@ import logging from pathlib import Path from typing import Optional +import click import typer from dotenv import load_dotenv +from flask.cli import FlaskGroup from rich.logging import RichHandler +import ttfrog.app + CONFIG_DEFAULTS = """ # ttfrog Defaults LOG_LEVEL=INFO """ -app = typer.Typer() +main_app = typer.Typer() + +_context = ttfrog.app.initialize() +flask_app = _context.flask + app_state = dict( config_file=Path("~/.config/ttfrog.conf").expanduser(), ) @@ -21,8 +29,8 @@ app_state = dict( logger = logging.getLogger("ttfrog.cli") -@app.callback(invoke_without_command=True) -def main( +@main_app.callback(invoke_without_command=True) +def callback( context: typer.Context, verbose: bool = typer.Option(False, help="Enable verbose output."), log_level: str = typer.Option("error", help=" Set the log level."), @@ -54,4 +62,33 @@ def run(context: typer.Context): """ The default CLI entrypoint is ttfrog.cli.run(). """ - raise NotImplementedError("Please define ttfrog.cli.run().") + flask_app.run() + + +@flask_app.shell_context_processor +def make_shell_context(): + return {"db": flask_app.db, "app": flask_app._app} + + +@flask_app.cli.command() +@click.option("--drop", is_flag=True, help="Create after drop.") +@click.pass_context +def setup(ctx, drop: bool): + """ + (Re)create the database. + """ + if drop: + ctx.db.drop_all() + ctx.db.create_all() + + +@click.group(cls=FlaskGroup, create_app=lambda: flask_app) +@click.pass_context +def app(ctx): + """ + Application management functions + """ + + +main = typer.main.get_command(main_app) +main.add_command(app, "app") diff --git a/src/ttfrog/db.py b/src/ttfrog/db.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ttfrog/schema.ph b/src/ttfrog/schema.ph new file mode 100644 index 0000000..b540bc0 --- /dev/null +++ b/src/ttfrog/schema.ph @@ -0,0 +1 @@ +# schema goes here diff --git a/src/ttfrog/schema.py b/src/ttfrog/schema.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ttfrog/web.py b/src/ttfrog/web.py new file mode 100644 index 0000000..745c643 --- /dev/null +++ b/src/ttfrog/web.py @@ -0,0 +1,9 @@ +from ttfrog.app import initialize + +_context = initialize() +app = _context.flask + + +@app.route("/", defaults={"path": ""}) +def serve(path): + return "HELLO"