Skip to content

Commit 27a0e17

Browse files
committed
[ADD] add rich outputs & move template dir into package folder, edit testcases
1 parent dec669a commit 27a0e17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+247
-122
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ FastAPI-fastkit uses following stacks:
4848

4949
(content will be added later)
5050
(this block will contain about dependancy settings using virtual env, project tree, file extension of all sources(must end with .py-tpl),
51-
README.md writing guide(using [PROJECT_README_TEMPLATE.md](fastapi-project-template/PROJECT_README_TEMPLATE.md) template), foldering, other third party config, etc...)
51+
README.md writing guide(using [PROJECT_README_TEMPLATE.md](src/fastapi_fastkit/fastapi_project_template/PROJECT_README_TEMPLATE.md) template), foldering, other third party config, etc...)
5252

5353
## Additional note
5454

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
recursive-include src/fastapi_fastkit/fastapi_project_template *
2+
recursive-exclude src/fastapi_fastkit/fastapi_project_template *.pyc
3+
recursive-exclude src/fastapi_fastkit/fastapi_project_template *.pyo
4+
recursive-exclude src/fastapi_fastkit/fastapi_project_template __pycache__

fastapi-project-template/fastapi-default/README.md-tpl

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/fastapi_fastkit/backend.py

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,73 @@
77
import os
88
import click
99

10-
from typing import Any, Union
10+
from typing import Any
1111

1212
from logging import getLogger
13-
1413
from click.core import Context
14+
from rich.panel import Panel
15+
from rich.console import Console
16+
from rich.text import Text
17+
from rich.table import Table
1518

1619
from fastapi_fastkit.core.exceptions import TemplateExceptions
1720

18-
1921
logger = getLogger(__name__)
2022

23+
if "PYTEST_CURRENT_TEST" in os.environ:
24+
console = Console(no_color=True)
25+
else:
26+
console = Console()
27+
2128
REGEX = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b"
2229

2330

31+
def print_error(message: str, title: str = "Error") -> None:
32+
"""Print an error message with specified output style."""
33+
error_text = Text()
34+
error_text.append("❌ ", style="bold red")
35+
error_text.append(message)
36+
console.print(Panel(error_text, border_style="red", title=title))
37+
38+
39+
def print_success(message: str, title: str = "Success") -> None:
40+
"""Print a success message with specified output style."""
41+
success_text = Text()
42+
success_text.append("✨ ", style="bold yellow")
43+
success_text.append(message, style="bold green")
44+
console.print(Panel(success_text, border_style="green", title=title))
45+
46+
47+
def print_warning(message: str, title: str = "Warning") -> None:
48+
"""Print a warning message with specified output style."""
49+
warning_text = Text()
50+
warning_text.append("⚠️ ", style="bold yellow")
51+
warning_text.append(message)
52+
console.print(Panel(warning_text, border_style="yellow", title=title))
53+
54+
55+
def create_info_table(
56+
title: str, data: dict[str, str], show_header: bool = False
57+
) -> Table:
58+
"""Create a table for displaying information."""
59+
table = Table(title=title, show_header=show_header, title_style="bold magenta")
60+
table.add_column("Field", style="cyan")
61+
table.add_column("Value", style="green")
62+
63+
for key, value in data.items():
64+
table.add_row(key, value)
65+
66+
return table
67+
68+
2469
def validate_email(ctx: Context, param: Any, value: Any) -> Any:
25-
"""
26-
Check if the provided email is in a valid format.
27-
This will recursively loop until a valid email input entry is given.
28-
29-
:param ctx: context of passing configurations (NOT specify it at CLI)
30-
:type ctx: <Object click.Context>
31-
:param param: parameters from CLI
32-
:param value: values from CLI
33-
:return:
34-
"""
70+
"""Validate email format."""
3571
try:
3672
if not re.match(REGEX, value):
3773
raise ValueError(value)
38-
else:
39-
return value
74+
return value
4075
except ValueError as e:
41-
click.echo("Incorrect email address given: {}".format(e))
76+
print_error(f"Incorrect email address given: {e}")
4277
value = click.prompt(param.prompt)
4378
return validate_email(ctx, param, value)
4479

@@ -50,19 +85,11 @@ def inject_project_metadata(
5085
author_email: str,
5186
description: str,
5287
) -> None:
53-
"""
54-
Inject metadata into the main.py and setup.py files.
55-
56-
:param target_dir: Directory for the new project to deploy
57-
:param project_name: new project name
58-
:param author: cli username
59-
:param author_email: cli user email
60-
:param description: new project description
61-
"""
62-
main_py_path = os.path.join(target_dir, "main.py")
63-
setup_py_path = os.path.join(target_dir, "setup.py")
64-
88+
"""Inject project metadata."""
6589
try:
90+
main_py_path = os.path.join(target_dir, "main.py")
91+
setup_py_path = os.path.join(target_dir, "setup.py")
92+
6693
with open(main_py_path, "r+") as f:
6794
content = f.read()
6895
content = content.replace("app_title", f'"{project_name}"')
@@ -81,8 +108,8 @@ def inject_project_metadata(
81108
f.write(content)
82109
f.truncate()
83110
except Exception as e:
84-
click.echo(e)
85-
raise TemplateExceptions("ERROR : Having some errors with injecting metadata")
111+
print_error(f"Error during metadata injection: {e}")
112+
raise TemplateExceptions("Failed to inject metadata")
86113

87114

88115
# TODO : modify this function

src/fastapi_fastkit/cli.py

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# TODO : make logic with click(cli operation) & rich(decorate console outputs and indicate manuals)
21
# --------------------------------------------------------------------------
32
# The Module defines main and core CLI operations for FastAPI-fastkit.
43
#
@@ -17,9 +16,17 @@
1716

1817
from rich import print
1918
from rich.panel import Panel
19+
from rich.console import Console
2020

2121
from . import __version__
22-
from .backend import validate_email, inject_project_metadata
22+
from .backend import (
23+
validate_email,
24+
inject_project_metadata,
25+
print_error,
26+
print_success,
27+
print_warning,
28+
create_info_table,
29+
)
2330
from fastapi_fastkit.utils.logging import setup_logging
2431
from fastapi_fastkit.core.settings import FastkitConfig
2532
from fastapi_fastkit.core.exceptions import CLIExceptions
@@ -29,6 +36,8 @@
2936

3037
logger = getLogger(__name__)
3138

39+
console = Console()
40+
3241

3342
@click.group()
3443
@click.option("--debug/--no-debug", default=False)
@@ -48,13 +57,7 @@ def fastkit_cli(ctx: Context, debug: bool) -> Union["BaseCommand", None]:
4857
ctx.ensure_object(dict)
4958

5059
if debug:
51-
warning_panel = Panel(
52-
"running at debugging mode!!",
53-
title="❗️ Warning ❗",
54-
style="yellow",
55-
highlight=True,
56-
)
57-
click.echo(print(warning_panel))
60+
print_warning("running at debugging mode!!")
5861
settings.set_debug_mode()
5962

6063
ctx.obj["settings"] = settings
@@ -98,27 +101,28 @@ def echo(ctx: Context) -> None:
98101
def list_templates() -> None:
99102
"""
100103
Display the list of available templates.
101-
102-
:return: None
103104
"""
104105
settings = FastkitConfig()
105106
template_dir = settings.FASTKIT_TEMPLATE_ROOT
106107

107108
if not os.path.exists(template_dir):
108-
click.echo("Template directory not found.")
109+
print_error("Template directory not found.")
109110
return
110111

111112
templates = [
112113
d
113114
for d in os.listdir(template_dir)
114-
if os.path.isdir(os.path.join(template_dir, d))
115+
if os.path.isdir(os.path.join(template_dir, d)) and d != "__pycache__"
115116
]
116117

117118
if not templates:
118-
click.echo("No available templates.")
119+
print_warning("No available templates.")
119120
return
120121

121-
click.echo("\nAvailable templates:")
122+
table = create_info_table(
123+
"Available Templates", {template: "No description" for template in templates}
124+
)
125+
122126
for template in templates:
123127
template_path = os.path.join(template_dir, template)
124128
readme_path = os.path.join(template_path, "README.md-tpl")
@@ -130,7 +134,9 @@ def list_templates() -> None:
130134
if first_line.startswith("# "):
131135
description = first_line[2:]
132136

133-
click.echo(f"- {template}: {description}")
137+
table.add_row(template, description)
138+
139+
console.print(table)
134140

135141

136142
@fastkit_cli.command(context_settings={"ignore_unknown_options": True})
@@ -183,21 +189,29 @@ def startup(
183189
print(f"Template path: {target_template}")
184190

185191
if not os.path.exists(target_template):
192+
print_error(f"Template '{template}' does not exist in '{template_dir}'.")
186193
raise CLIExceptions(
187-
f"Error: Template '{template}' does not exist in '{template_dir}'."
194+
f"Template '{template}' does not exist in '{template_dir}'."
188195
)
189-
click.echo(f"\nProject Name: {project_name}")
190-
click.echo(f"Author: {author}")
191-
click.echo(f"Author Email: {author_email}")
192-
click.echo(f"Description: {description}")
193-
# read_template_stack()
196+
table = create_info_table(
197+
"Project Information",
198+
{
199+
"Project Name": project_name,
200+
"Author": author,
201+
"Author Email": author_email,
202+
"Description": description,
203+
},
204+
)
205+
206+
console.print("\n")
207+
console.print(table)
194208
# click.echo("Project Stack: [FastAPI, Uvicorn, SQLAlchemy, Docker (optional)]") # TODO : impl this?
195209

196210
confirm = click.confirm(
197211
"\nDo you want to proceed with project creation?", default=False
198212
)
199213
if not confirm:
200-
click.echo("Project creation aborted!")
214+
print_error("Project creation aborted!")
201215
return
202216

203217
try:
@@ -212,11 +226,12 @@ def startup(
212226
project_dir, project_name, author, author_email, description
213227
)
214228

215-
click.echo(
229+
print_success(
216230
f"FastAPI project '{project_name}' from '{template}' has been created and saved to {user_local}!"
217231
)
232+
218233
except Exception as e:
219-
click.echo(f"Error during project creation: {e}")
234+
print_error(f"Error during project creation: {e}")
220235

221236

222237
@fastkit_cli.command(context_settings={"ignore_unknown_options": True})
@@ -244,12 +259,16 @@ def startproject(project_name: str, stack: str) -> None:
244259
project_dir = os.path.join(settings.USER_WORKSPACE, project_name)
245260

246261
if os.path.exists(project_dir):
247-
click.echo(f"Error: Project '{project_name}' already exists.")
262+
print_error(f"Error: Project '{project_name}' already exists.")
248263
return
249264

250265
try:
251266
os.makedirs(project_dir)
252267

268+
table = create_info_table(
269+
f"Creating Project: {project_name}", {"Component": "Status"}
270+
)
271+
253272
dependencies = {
254273
"minimal": ["fastapi", "uvicorn"],
255274
"standard": ["fastapi", "uvicorn", "sqlalchemy", "alembic", "pytest"],
@@ -268,15 +287,23 @@ def startproject(project_name: str, stack: str) -> None:
268287
with open(os.path.join(project_dir, "requirements.txt"), "w") as f:
269288
for dep in dependencies[stack]:
270289
f.write(f"{dep}\n")
290+
table.add_row(dep, "✓")
271291

272-
click.echo("Creating virtual environment and installing dependencies...")
273-
subprocess.run(["python", "-m", "venv", os.path.join(project_dir, "venv")])
274-
subprocess.run(["pip", "install", "-r", "requirements.txt"], cwd=project_dir)
292+
console.print(table)
275293

276-
click.echo(f"Project '{project_name}' has been created successfully!")
294+
with console.status("[bold green]Setting up project environment..."):
295+
console.print("[yellow]Creating virtual environment...[/yellow]")
296+
subprocess.run(["python", "-m", "venv", os.path.join(project_dir, "venv")])
297+
298+
console.print("[yellow]Installing dependencies...[/yellow]")
299+
subprocess.run(
300+
["pip", "install", "-r", "requirements.txt"], cwd=project_dir
301+
)
302+
303+
print_success(f"Project '{project_name}' has been created successfully!")
277304

278305
except Exception as e:
279-
click.echo(f"Error during project creation: {e}")
306+
print_error(f"Error during project creation: {e}")
280307
shutil.rmtree(project_dir, ignore_errors=True)
281308

282309

@@ -316,28 +343,27 @@ def deleteproject(ctx: Context, project_name: str) -> None:
316343
project_dir = os.path.join(user_local, project_name)
317344

318345
if not os.path.exists(project_dir):
319-
click.echo(f"Error: Project '{project_name}' does not exist in '{user_local}'.")
346+
print_error(f"Project '{project_name}' does not exist in '{user_local}'.")
320347
return
321348

322349
if not is_fastkit_project(project_dir):
323-
click.echo(f"Error: '{project_name}' is not a FastAPI-fastkit project.")
350+
print_error(f"'{project_name}' is not a FastAPI-fastkit project.")
324351
return
325352

326353
confirm = click.confirm(
327354
f"\nDo you want to delete project '{project_name}' at '{project_dir}'?",
328355
default=False,
329356
)
330357
if not confirm:
331-
click.echo("Project deletion cancelled!")
358+
print_error("Project deletion cancelled!")
332359
return
333360

334361
try:
335362
delete_project(project_dir)
336-
click.echo(
337-
f"Project '{project_name}' has been successfully deleted from '{user_local}'."
338-
)
363+
print_success(f"Project '{project_name}' has been deleted successfully!")
364+
339365
except Exception as e:
340-
click.echo(f"Error during project deletion: {e}")
366+
print_error(f"Error during project deletion: {e}")
341367

342368

343369
@fastkit_cli.command()
@@ -388,7 +414,7 @@ def runserver(
388414

389415
app_path = os.path.join(project_dir, "main.py")
390416
if not os.path.exists(app_path):
391-
click.echo(f"Error: Could not find 'main.py' in '{project_dir}'.")
417+
print_error(f"Could not find 'main.py' in '{project_dir}'.")
392418
return
393419

394420
command = [
@@ -406,7 +432,7 @@ def runserver(
406432
command.append("--reload")
407433

408434
try:
409-
click.echo(f"Starting FastAPI server at {host}:{port}...")
435+
print_success(f"Starting FastAPI server at {host}:{port}...")
410436
subprocess.run(command, check=True)
411437
except subprocess.CalledProcessError as e:
412-
click.echo(f"Error: Failed to start FastAPI server.\n{e}")
438+
print_error(f"Failed to start FastAPI server.\n{e}")

0 commit comments

Comments
 (0)