From 4ef522de7e129ce1902a4bfd1f69ca595b4be5ed Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 25 Nov 2020 23:19:53 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20startapp=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 1 + ROADMAP.md | 3 +++ manage_fastapi/{schemas.py => context.py} | 16 +++++++++++++-- manage_fastapi/generator.py | 20 +++++++++++++++---- manage_fastapi/main.py | 13 ++++++------ manage_fastapi/templates/app/__init__.py | 0 .../templates/app/cookiecutter.json | 5 +++++ .../templates/app/hooks/post_gen_project.py | 0 .../__init__.py | 0 .../api/__init__.py | 0 .../{{ cookiecutter.folder_name }}/api/v1.py | 8 ++++++++ .../{{ cookiecutter.folder_name }}/crud.py | 0 .../{{ cookiecutter.folder_name }}/models.py | 0 .../{{ cookiecutter.folder_name }}/schemas.py | 0 .../project/hooks/post_gen_project.py | 16 +++++++++------ tests/test_startproject.py | 3 ++- 16 files changed, 66 insertions(+), 19 deletions(-) rename manage_fastapi/{schemas.py => context.py} (73%) create mode 100644 manage_fastapi/templates/app/__init__.py create mode 100644 manage_fastapi/templates/app/cookiecutter.json create mode 100644 manage_fastapi/templates/app/hooks/post_gen_project.py create mode 100644 manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/__init__.py create mode 100644 manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/__init__.py create mode 100644 manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/v1.py create mode 100644 manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/crud.py create mode 100644 manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/models.py create mode 100644 manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/schemas.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47ec539..2b1f9cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,7 @@ repos: rev: v5.4.2 hooks: - id: isort + exclude: .*/templates/.* args: ["--profile", "black"] - repo: local hooks: diff --git a/ROADMAP.md b/ROADMAP.md index 5f84b0d..dce32e9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,6 +15,9 @@ The package plans are here. If you want to contribute with new ideas, or develop * [X] Integrate databases on `startproject`. - [X] Postgres * [ ] Different Authentication support on `startproject`. +* [X] Support `startapp` command. + - [X] Simple app creation. + - [ ] Append the APIRouter to the FastAPI app. * [ ] Add tests. * [ ] Fix documentation accordingly. diff --git a/manage_fastapi/schemas.py b/manage_fastapi/context.py similarity index 73% rename from manage_fastapi/schemas.py rename to manage_fastapi/context.py index 3eb60fe..5b8dc20 100644 --- a/manage_fastapi/schemas.py +++ b/manage_fastapi/context.py @@ -8,7 +8,19 @@ from manage_fastapi.config import FASTAPI_VERSION from manage_fastapi.constants import Database, License, PackageManager, PythonVersion -class Context(BaseModel): +class AppContext(BaseModel): + name: str + folder_name: str + snake_name: str + + @root_validator(pre=True) + def validate_app(cls, values: dict): + values["folder_name"] = values["name"].lower().replace(" ", "-").strip() + values["snake_name"] = values["folder_name"].replace("-", "_") + return values + + +class ProjectContext(BaseModel): name: str folder_name: str packaging: PackageManager @@ -28,7 +40,7 @@ class Context(BaseModel): database: Optional[Database] @root_validator(pre=True) - def git_info(cls, values: dict): + def validate_project(cls, values: dict): try: values["username"] = subprocess.check_output( ["git", "config", "--get", "user.name"] diff --git a/manage_fastapi/generator.py b/manage_fastapi/generator.py index 2517b1d..21fb8d9 100644 --- a/manage_fastapi/generator.py +++ b/manage_fastapi/generator.py @@ -1,21 +1,33 @@ import os +from typing import TypeVar import typer from cookiecutter.exceptions import OutputDirExistsException from cookiecutter.main import cookiecutter +from pydantic.main import BaseModel from manage_fastapi.config import TEMPLATES_DIR -from manage_fastapi.schemas import Context +from manage_fastapi.context import AppContext, ProjectContext + +ContextType = TypeVar("ContextType", bound=BaseModel) -def generate_project(context: Context): +def fill_template(template_name: str, context: ContextType): try: cookiecutter( - os.path.join(TEMPLATES_DIR, "project"), + os.path.join(TEMPLATES_DIR, template_name), extra_context=context.dict(), no_input=True, ) except OutputDirExistsException: typer.echo(f"Folder '{context.folder_name}' already exists. 😞") else: - typer.echo("FastAPI project created successfully! 🎉") + typer.echo(f"FastAPI {template_name} created successfully! 🎉") + + +def generate_app(context: AppContext): + fill_template("app", context) + + +def generate_project(context: ProjectContext): + fill_template("project", context) diff --git a/manage_fastapi/main.py b/manage_fastapi/main.py index 4752dcb..2e6e435 100644 --- a/manage_fastapi/main.py +++ b/manage_fastapi/main.py @@ -6,9 +6,9 @@ import pkg_resources import typer from manage_fastapi.constants import Database, License, PackageManager, PythonVersion -from manage_fastapi.generator import generate_project +from manage_fastapi.context import AppContext, ProjectContext +from manage_fastapi.generator import generate_app, generate_project from manage_fastapi.helpers import bullet, launch_cli, yes_no -from manage_fastapi.schemas import Context app = typer.Typer(help="Managing FastAPI projects made easy!", name="Manage FastAPI") @@ -33,9 +33,9 @@ def startproject( ("docker", yes_no("docker")), ("database", bullet(Database)), ) - context = Context(name=name, **result) + context = ProjectContext(name=name, **result) else: - context = Context( + context = ProjectContext( name=name, packaging=packaging, python=python, @@ -48,8 +48,9 @@ def startproject( @app.command(help="Creates a FastAPI component.") -def startapp(): - ... +def startapp(name: str): + context = AppContext(name=name) + generate_app(context) @app.command(help="Run a FastAPI application.") diff --git a/manage_fastapi/templates/app/__init__.py b/manage_fastapi/templates/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/app/cookiecutter.json b/manage_fastapi/templates/app/cookiecutter.json new file mode 100644 index 0000000..6208572 --- /dev/null +++ b/manage_fastapi/templates/app/cookiecutter.json @@ -0,0 +1,5 @@ +{ + "folder_name": "{{ cookiecutter.folder_name }}", + "name": "{{ cookiecutter.name }}", + "snake_name": "{{ cookiecutter.snake_name }}" +} diff --git a/manage_fastapi/templates/app/hooks/post_gen_project.py b/manage_fastapi/templates/app/hooks/post_gen_project.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/__init__.py b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/__init__.py b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/v1.py b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/v1.py new file mode 100644 index 0000000..2fb3208 --- /dev/null +++ b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/api/v1.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/") +def get_{{ cookiecutter.snake_name }}(): + return "{{ cookiecutter.name }} app created!" diff --git a/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/crud.py b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/crud.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/models.py b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/models.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/schemas.py b/manage_fastapi/templates/app/{{ cookiecutter.folder_name }}/schemas.py new file mode 100644 index 0000000..e69de29 diff --git a/manage_fastapi/templates/project/hooks/post_gen_project.py b/manage_fastapi/templates/project/hooks/post_gen_project.py index f4c7f4d..853d692 100644 --- a/manage_fastapi/templates/project/hooks/post_gen_project.py +++ b/manage_fastapi/templates/project/hooks/post_gen_project.py @@ -17,14 +17,10 @@ def remove_paths(paths: list): def set_packaging(): packaging = "{{ cookiecutter.packaging }}" - paths = [] - if packaging == PackageManager.PIP: - paths = ["poetry.lock", "pyproject.toml"] + remove_paths(["poetry.lock", "pyproject.toml"]) elif packaging == PackageManager.POETRY: - paths = ["requirements.txt"] - - remove_paths(paths) + remove_paths(["requirements.txt"]) def set_pre_commit(): @@ -51,6 +47,14 @@ def set_license(): remove_paths(["LICENSE"]) +# def set_config_location(): +# database = "{{ cookiecutter.database }}" +# if database == "None": +# remove_paths(["app/core/config.py"]) +# else: +# remove_paths(["app/config.py"]) + + def main(): set_database() set_docker() diff --git a/tests/test_startproject.py b/tests/test_startproject.py index 2f3544e..6756e0d 100644 --- a/tests/test_startproject.py +++ b/tests/test_startproject.py @@ -1,9 +1,10 @@ from unittest.mock import patch import pytest -from manage_fastapi.main import app from typer.testing import CliRunner +from manage_fastapi.main import app + runner = CliRunner() CREATED_SUCCESSFULLY = "FastAPI project created successfully! 🎉\n"