From 30475bbdf56862662d1b477c4942cf8ef37b9641 Mon Sep 17 00:00:00 2001 From: David Carmichael Date: Thu, 8 Aug 2024 20:36:05 +0100 Subject: [PATCH] feat: add api blueprint generator --- pyproject.toml | 2 +- src/flask_imp/_cli/__init__.py | 46 +++++++++--- src/flask_imp/_cli/blueprint.py | 77 ++++++++++++++------- src/flask_imp/_cli/filelib/api_blueprint.py | 24 +++++++ src/flask_imp/_cli/filelib/blueprint.py | 2 +- src/flask_imp/_cli/helpers.py | 40 +++++++++++ src/flask_imp/_cli/init.py | 28 +------- 7 files changed, 153 insertions(+), 66 deletions(-) create mode 100644 src/flask_imp/_cli/filelib/api_blueprint.py diff --git a/pyproject.toml b/pyproject.toml index 189055df..5140da07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "flask-imp" -version = "5.1.2" +version = "5.2.0" description = 'A Flask auto importer that allows your Flask apps to grow big.' authors = [{ name = "David Carmichael", email = "david@uilix.com" }] readme = "README.md" diff --git a/src/flask_imp/_cli/__init__.py b/src/flask_imp/_cli/__init__.py index 24098fe7..ec72bce3 100644 --- a/src/flask_imp/_cli/__init__.py +++ b/src/flask_imp/_cli/__init__.py @@ -1,6 +1,7 @@ import click from .blueprint import add_blueprint as _add_blueprint +from .blueprint import add_api_blueprint as _add_api_blueprint from .helpers import Sprinkles as Sp from .init import init_app as _init_app @@ -15,9 +16,9 @@ def cli() -> None: "-n", "--name", nargs=1, - default="my_new_blueprint", - prompt="Name of the blueprint to create", - help="The name of the blueprint to create", + default="new_blueprint", + prompt="Name", + help="The name of the blueprint to create.", ) @click.option( "-f", @@ -25,28 +26,51 @@ def cli() -> None: nargs=1, default=".", prompt=( - f"\n{Sp.WARNING}(Creation is relative to the current working directory){Sp.END}\n" - f"Folder to create blueprint in" + f"Folder {Sp.WARNING}(relative to CWD){Sp.END}" ), - help="The from_folder to create the blueprint in, defaults to the current working directory", + help="The folder to create the blueprint in, creation is relative to the current working directory.", ) def add_blueprint(name: str, folder: str) -> None: _add_blueprint(name=name, folder=folder) -@cli.command("init", help="Create a new flask-imp app") + +@cli.command("api-blueprint", help="Create a flask-imp api blueprint") +@click.option( + "-n", + "--name", + nargs=1, + default="new_api_blueprint", + prompt="Name", + help="The name of the api blueprint to create.", +) +@click.option( + "-f", + "--folder", + nargs=1, + default=".", + prompt=( + f"Folder {Sp.WARNING}(relative to CWD){Sp.END}" + ), + help="The folder to create the api blueprint in, creation is relative to the current working directory.", +) +def add_api_blueprint(name: str, folder: str) -> None: + _add_api_blueprint(name=name, folder=folder) + + +@cli.command("init", help="Create a new flask-imp app.") @click.option( "-n", "--name", nargs=1, default=None, - help="The name of the app folder that will be created", + help="The name of the app folder that will be created.", ) -@click.option("-s", "--slim", is_flag=True, default=False, help="Create a slim app") +@click.option("-s", "--slim", is_flag=True, default=False, help="Create a slim app.") @click.option( - "-m", "--minimal", is_flag=True, default=False, help="Create a minimal app" + "-m", "--minimal", is_flag=True, default=False, help="Create a minimal app." ) -@click.option("-f", "--full", is_flag=True, default=False, help="Create a full app") +@click.option("-f", "--full", is_flag=True, default=False, help="Create a full app.") def init_new_app(name: str, full: bool, slim: bool, minimal: bool) -> None: if not full and not slim and not minimal: choice = click.prompt( diff --git a/src/flask_imp/_cli/blueprint.py b/src/flask_imp/_cli/blueprint.py index e0931de7..41c9b754 100644 --- a/src/flask_imp/_cli/blueprint.py +++ b/src/flask_imp/_cli/blueprint.py @@ -9,6 +9,56 @@ from .filelib.water_css import water_css from .helpers import Sprinkles as Sp from .helpers import to_snake_case +from .helpers import build + + +def add_api_blueprint( + name: str = "new_api_blueprint", + folder: str = ".", + _init_app: bool = False, + _cwd: t.Optional[Path] = None, + _url_prefix: t.Optional[str] = None, +) -> None: + from .filelib.api_blueprint import api_blueprint_init_py + from .filelib.api_blueprint import api_blueprint_routes_index_py + + click.echo(f"{Sp.OKGREEN}Creating API Blueprint: {name}") + + if _cwd: + cwd = _cwd + else: + cwd = Path.cwd() + + if not cwd.exists(): + click.echo(f"{Sp.FAIL}{folder} does not exist.{Sp.END}") + return + + name = to_snake_case(name) + + if folder == ".": + root_folder = cwd / name + else: + root_folder = cwd / folder / name + + folders: t.Dict[str, Path] = { + "root": root_folder, + "routes": root_folder / "routes", + } + + files: t.Dict[str, t.Tuple[Path, t.Any]] = { + "root/__init__.py": ( + folders["root"] / "__init__.py", + api_blueprint_init_py( + url_prefix=name if not _url_prefix else _url_prefix, name=name + ), + ), + "routes/index.py": ( + folders["routes"] / "index.py", + api_blueprint_routes_index_py(), + ), + } + + build(folders, files, building="API Blueprint") def add_blueprint( @@ -112,29 +162,4 @@ def add_blueprint( ), } - for folder, path in folders.items(): - if not path.exists(): - path.mkdir(parents=True) - click.echo(f"{Sp.OKGREEN}Blueprint folder: {folder}, created{Sp.END}") - else: - click.echo( - f"{Sp.WARNING}Blueprint folder already exists: {folder}, skipping{Sp.END}" - ) - - for file, (path, content) in files.items(): - if not path.exists(): - if file == "static/img/flask-imp-logo.png": - path.write_bytes(bytes.fromhex(content)) - continue - - if not content: - print(path) - path.write_text(content, encoding="utf-8") - - click.echo(f"{Sp.OKGREEN}Blueprint file: {file}, created{Sp.END}") - else: - click.echo( - f"{Sp.WARNING}Blueprint file already exists: {file}, skipping{Sp.END}" - ) - - click.echo(f"{Sp.OKGREEN}Blueprint created: {folders['root']}{Sp.END}") + build(folders, files, building="Blueprint") diff --git a/src/flask_imp/_cli/filelib/api_blueprint.py b/src/flask_imp/_cli/filelib/api_blueprint.py new file mode 100644 index 00000000..ed4b5f10 --- /dev/null +++ b/src/flask_imp/_cli/filelib/api_blueprint.py @@ -0,0 +1,24 @@ +def api_blueprint_init_py(url_prefix: str, name: str) -> str: + return f"""\ +from flask_imp import ImpBlueprint +from flask_imp.config import ImpBlueprintConfig + +bp = ImpBlueprint(__name__, ImpBlueprintConfig( + enabled=True, + url_prefix="/{url_prefix}", + init_session={{"{name}_session_loaded": True}}, +)) + +bp.import_resources("routes") +""" + + +def api_blueprint_routes_index_py() -> str: + return """\ +from .. import bp + + +@bp.route("/", methods=["GET"]) +def index(): + return {"message": "Hello, World!"} +""" diff --git a/src/flask_imp/_cli/filelib/blueprint.py b/src/flask_imp/_cli/filelib/blueprint.py index bcbb118e..2ff14a47 100644 --- a/src/flask_imp/_cli/filelib/blueprint.py +++ b/src/flask_imp/_cli/filelib/blueprint.py @@ -9,7 +9,7 @@ def blueprint_init_py(url_prefix: str, name: str) -> str: bp = ImpBlueprint(__name__, ImpBlueprintConfig( enabled=True, url_prefix="/{url_prefix}", - init_session={{ "{name}_session_loaded": True }}, + init_session={{"{name}_session_loaded": True}}, )) bp.import_resources("routes") diff --git a/src/flask_imp/_cli/helpers.py b/src/flask_imp/_cli/helpers.py index 86da6fb4..e0143f89 100644 --- a/src/flask_imp/_cli/helpers.py +++ b/src/flask_imp/_cli/helpers.py @@ -1,4 +1,7 @@ import re +import typing as t + +import click def to_snake_case(string: str) -> str: @@ -24,3 +27,40 @@ class Sprinkles: BOLD = "\033[1m" UNDERLINE = "\033[4m" END = "\033[0m" + + +def build( + folders: t.Dict[str, t.Any], files: t.Dict[str, t.Any], building: str = "App" +) -> None: + write_bytes: t.List[str] = [ + "resources/static/favicon.ico", + "resources/static/img/flask-imp-logo.png", + "static/img/flask-imp-logo.png", + ] + + for folder, path in folders.items(): + if not path.exists(): + path.mkdir(parents=True) + click.echo( + f"{Sprinkles.OKGREEN}{building} folder: {folder}, created{Sprinkles.END}" + ) + else: + click.echo( + f"{Sprinkles.WARNING}{building} folder already exists: {folder}, skipping{Sprinkles.END}" + ) + + for file, (path, content) in files.items(): + if not path.exists(): + if file in write_bytes: + path.write_bytes(bytes.fromhex(content)) + continue + + path.write_text(content, encoding="utf-8") + + click.echo( + f"{Sprinkles.OKGREEN}{building} file: {file}, created{Sprinkles.END}" + ) + else: + click.echo( + f"{Sprinkles.WARNING}{building} file already exists: {file}, skipping{Sprinkles.END}" + ) diff --git a/src/flask_imp/_cli/init.py b/src/flask_imp/_cli/init.py index 891bedc0..c0d9f3e2 100644 --- a/src/flask_imp/_cli/init.py +++ b/src/flask_imp/_cli/init.py @@ -1,5 +1,4 @@ import os -import typing as t from pathlib import Path import click @@ -10,32 +9,7 @@ from .filelib.head_tag_generator import head_tag_generator from .filelib.water_css import water_css from .helpers import Sprinkles as Sp - - -def build(folders: t.Dict[str, t.Any], files: t.Dict[str, t.Any]) -> None: - for folder, path in folders.items(): - if not path.exists(): - path.mkdir(parents=True) - click.echo(f"{Sp.OKGREEN}App folder: {folder}, created{Sp.END}") - else: - click.echo( - f"{Sp.WARNING}App folder already exists: {folder}, skipping{Sp.END}" - ) - - for file, (path, content) in files.items(): - if not path.exists(): - if ( - file == "resources/static/favicon.ico" - or file == "resources/static/img/flask-imp-logo.png" - ): - path.write_bytes(bytes.fromhex(content)) - continue - - path.write_text(content, encoding="utf-8") - - click.echo(f"{Sp.OKGREEN}App file: {file}, created{Sp.END}") - else: - click.echo(f"{Sp.WARNING}App file already exists: {file}, skipping{Sp.END}") +from .helpers import build def minimal_app(app_folder: Path) -> None: