Skip to content

Commit 8ca0be2

Browse files
author
Brian Obot
committed
Initial Attempts to Refactor codebase and improve on tests coverage
1 parent a3630c9 commit 8ca0be2

File tree

7 files changed

+157
-85
lines changed

7 files changed

+157
-85
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
faker

src/fastapi_gen8/helpers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,60 @@
11
import subprocess
22

33

4+
def display_intro_text() -> None:
5+
"""
6+
Displays the Introduction Text of the Library as a Nice and Friendly UI
7+
Alongside a short instructions on how to use the library for new users.
8+
"""
9+
intro_message = """
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+
print(intro_message)
35+
description = """
36+
Generate a fully structured FastAPI projects instantly.
37+
Boilerplate code, ready-to-run endpoints, and project scaffolding
38+
all in one simple command. Kickstart your backend in seconds!
39+
40+
Provide Project Details to each prompt and press 'Enter' to complete project setup
41+
42+
NOTES: Values placed within square brackets ([My Awesome FastAPI Project]) are defaults values for the project details
43+
If you do not provide a value for any particular, those values are used instead.
44+
45+
Have a Blast 🚀 - Brian
46+
_____________________________________________________________________________________________________
47+
"""
48+
# FUN 🚀
49+
50+
# How Fast Can you Complete your FastAPI project setup?
51+
# Blaze through the steps to make the global leaderboard for projects generated with FastAPI Project Gen8.
52+
# In Order to qualify for this leaderboard, you have to make sure to input every project detail and not use defaults
53+
# even though the defaults match your project attribute.
54+
# Current Best Record: {get_current_best_record()[0]} seconds - Title: [{get_current_best_record()[1]}]
55+
print(description)
56+
57+
458
def slugify(text: str) -> str:
559
return text.replace(" ", "_").replace("-", "_").lower()
660

src/fastapi_gen8/main.py

Lines changed: 53 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import os
22
import re
33
import time
4-
import subprocess
54

65
import argparse
6+
import subprocess
77
import importlib.metadata
88

99
from typing import cast
@@ -15,73 +15,61 @@
1515
success_print,
1616
warning_print,
1717
clone_repository,
18+
display_intro_text,
1819
)
1920

2021

21-
def intro_text() -> None:
22-
intro_message = """
23-
______________________________________________________________
24-
25-
███████╗ █████╗ ███████╗████████╗ █████╗ ██████╗ ██╗
26-
██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║
27-
█████╗ ███████║███████╗ ██║ ███████║██████╔╝██║
28-
██╔══╝ ██╔══██║╚════██║ ██║ ██╔══██║██ ██║
29-
██║ ██║ ██║███████║ ██║ ██║ ██║██║ ║██║
30-
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚╝╚╝
31-
32-
██████╗ ██████╗ ██████╗ ███████╗███████╗ ██████ ████████╗
33-
██╔══██╗██╔══██╗██╔═══██╗ ════██╗██╔════╝██╔════╝ ╚══██╔══╝
34-
██████╔╝██████╔╝██║ ██║ ██║█████╗ ██║ ██║
35-
██╔═══╝ ██╔══██╗██║ ██║███ ██║██╔══╝ ██║ ██║
36-
██║ ██║ ██║╚██████╔╝██████╔╝███████╗╚██████╗ ██║
37-
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝
38-
39-
██████╗ ███████╗███╗ ██╗ █████╗
40-
██╔════╝ ██╔════╝████╗ ██║██╔══██╗
41-
██║ ███╗█████╗ ██╔██╗ ██║ █████╔╝
42-
██║ ██║██╔══╝ ██║╚██╗██║██╔══██╗
43-
╚██████╔╝███████╗██║ ╚████║ █████╔╝
44-
╚═════╝ ╚══════╝╚═╝ ╚╝ ╚════╝
45-
______________________________________________________________
22+
def generate_default_project_details() -> dict[str, str | int | tuple]:
4623
"""
47-
print(intro_message)
48-
description = """
49-
Generate a fully structured FastAPI projects instantly.
50-
Boilerplate code, ready-to-run endpoints, and project scaffolding
51-
all in one simple command. Kickstart your backend in seconds!
24+
Generates Default Project Details
5225
53-
Provide Project Details to each prompt and press Enter complete project setup
54-
Values placed within square brackets ([My Awesome FastAPI Project]) are defaults values for the project details
55-
If you do not provide a value, those values are used instead
56-
_____________________________________________________________________________________________________
57-
"""
58-
# FUN 🚀
26+
For Single Value Constant Details like name, description etc
27+
The values are provided to the dictionary as simple String values
28+
But for Enumerated Values like open_source_license type
29+
the options are passed as a list of tuples where the the first item in tuple
30+
if the enumerate for the item and the second item is the actual value to be stored,
31+
32+
like so
5933
60-
# How Fast Can you Complete your FastAPI project setup?
61-
# Blaze through the steps to make the global leaderboard for projects generated with FastAPI Project Gen8.
62-
# In Order to qualify for this leaderboard, you have to make sure to input every project detail and not use defaults
63-
# even though the defaults match your project attribute.
64-
# Current Best Record: {get_current_best_record()[0]} seconds - Title: [{get_current_best_record()[1]}]
65-
print(description)
34+
open_source_license: (
35+
"<default_enumeration>", [
36+
(<enumeration>, "<actual_value>"),
37+
...
38+
]
39+
)
6640
41+
"""
42+
return {
43+
"name": "My Awesome FastAPI Project",
44+
"slug_name": "my_awesome_fastapi_project",
45+
"description": "FastAPI Project Description",
46+
"author(s)": ("John Doe", "Jane Doe"),
47+
"virtual_env_folder_name": "venv",
48+
"version": "0.1.0",
49+
"email": "brianobot9@gmail.com",
50+
"repository_link": "",
51+
"open_source_license": ("1", [
52+
(1, "MIT"),
53+
(2, "BSD"),
54+
(3, "GPLv3"),
55+
(4, "Apache Software License 2.0"),
56+
(5, "Not open source"),
57+
]),
58+
}
59+
6760

6861
def get_project_detail(
6962
attr: str,
7063
default: str | int | tuple[str, ...],
7164
project_detail: dict[str, str | int | tuple]
7265
) -> str | int | tuple[str, ...]:
73-
# Process the display for collecting the detail
74-
if "_" in attr:
75-
attr = attr.lower()
76-
if attr == "slug_name":
77-
# Update the slug name to match the project nanme
78-
default = slugify(str(project_detail['name']))
79-
if attr == "description":
80-
# Include the Project name in the default description for better suggestion
81-
print("About to Update the Description")
82-
default = cast(str, default) + f" for {project_detail['name']}"
83-
84-
if attr not in {"open_source_license", "username_type"}:
66+
"""
67+
This functions gets a project detail from the user via the command line interface, if nothing is provided
68+
the default value is used for the project detail value.
69+
"""
70+
71+
# Tuples Attrs means the detail have options, so process them differently
72+
if not isinstance(attr, tuple):
8573
detail = input(f"Enter Project {attr} ['{default}']: ")
8674
else:
8775
count = 0
@@ -120,7 +108,7 @@ def get_project_detail(
120108

121109
# Process the value provided by the user
122110
if attr == "slug_name":
123-
detail = slugify(cast(str, detail))
111+
detail = slugify(cast(str, detail)) if detail else None
124112
if attr == "authors":
125113
detail = tuple(cast(str, detail).split(","))
126114
return detail if detail else default
@@ -155,7 +143,7 @@ def apply_project_metadata(project_detail: dict[str, str]) -> None:
155143
target.write_text(content)
156144

157145

158-
def generate_project(project_detail: dict[str, str]):
146+
def generate_project_scaffold(project_detail: dict[str, str]):
159147
# Clone The Default Project Template into Folder with Project Slug Name
160148
# check if the project already exist
161149

@@ -207,6 +195,9 @@ def generate_project(project_detail: dict[str, str]):
207195

208196

209197
def main():
198+
"""
199+
Main entry point to interacting with the Command Line Utility of the Generator Library
200+
"""
210201
parser = argparse.ArgumentParser(
211202
prog="fastapi-gen8",
212203
description="Generate clean, production-ready FastAPI project scaffolds",
@@ -220,36 +211,13 @@ def main():
220211

221212
_args = parser.parse_args()
222213

223-
intro_text()
224-
225-
project_details: dict[str, str | int | tuple] = {
226-
"name": "My Awesome FastAPI Project",
227-
"slug_name": "my_awesome_fastapi_project",
228-
"description": "FastAPI Backend Project",
229-
"author(s)": ("John Doe", "Jane Doe"),
230-
"virtual_env_folder_name": "venv",
231-
"version": "0.1.0",
232-
"email": "brianobot9@gmail.com",
233-
"repository_link": "",
234-
"open_source_license": ("1", [
235-
(1, "MIT"),
236-
(2, "BSD"),
237-
(3, "GPLv3"),
238-
(4, "Apache Software License 2.0"),
239-
(5, "Not open source"),
240-
]),
241-
# "username_type": ("1", [
242-
# (1, "email"),
243-
# (2, "username"),
244-
# (3, "email + username"),
245-
# (4, "None"),
246-
# ]),
247-
}
214+
display_intro_text()
215+
project_details = generate_default_project_details()
248216

249-
start_time = time.time()
217+
start_time = time.time() # this is an internal metric to track the duration for each project generation
250218
for attr, default_value in project_details.items():
251219
detail = get_project_detail(attr.title(), default_value, project_details)
252-
project_details[attr] = detail
220+
project_details[attr] = detail # update the default project detail with the provided one
253221
success_print(f"Project {attr.title()} = {detail}")
254222

255223

@@ -259,7 +227,7 @@ def main():
259227
print("----------------------------------------------")
260228

261229
# Generate Projects with the Details Provided by the User
262-
generate_project(cast(dict[str, str], project_details))
230+
generate_project_scaffold(cast(dict[str, str], project_details))
263231

264232

265233
if __name__ == "__main__":
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import pytest
2+
import unittest.mock
3+
4+
from fastapi_gen8 import main
5+
6+
7+
def test_display_intro_text(capsys):
8+
main.display_intro_text()
9+
10+
captured = capsys.readouterr()
11+
assert main.display_intro_text.__doc__
12+
assert isinstance(captured.out, str)
13+
assert captured.err == ""
14+
15+
16+
def test_generate_default_project_details():
17+
result = main.generate_default_project_details()
18+
assert main.generate_default_project_details.__doc__
19+
assert isinstance(result, dict)
20+
assert all(isinstance(default_value, str | int | tuple) for default_value in result.values())
21+
22+
23+
@pytest.mark.parametrize(
24+
"attr,default_value,project_detail",
25+
[
26+
("name", "My Awesome FastAPI Project Test", main.generate_default_project_details()),
27+
("slug_name", "my_awesome_fastapi_project_test", main.generate_default_project_details()),
28+
("description", "FastAPI Project Description", main.generate_default_project_details()),
29+
("author(s)", ("John Doe", "Jane Doe"), main.generate_default_project_details()),
30+
("virtual_env_folder_name", "venv", main.generate_default_project_details()),
31+
("version", "0.0.1", main.generate_default_project_details()),
32+
("email", "brianobot9@gmail.com", main.generate_default_project_details()),
33+
("repository_url", "Default Name", main.generate_default_project_details()),
34+
("open_source_license", ("1", [
35+
(1, "MIT"),
36+
(2, "BSD"),
37+
(3, "GPLv3"),
38+
(4, "Apache Software License 2.0"),
39+
(5, "Not open source"),
40+
]), main.generate_default_project_details()),
41+
]
42+
)
43+
def test_get_project_detail(attr: str, default_value: str | int | tuple, project_detail: dict[str, str | int | tuple]):
44+
with unittest.mock.patch('builtins.input', side_effect=[None]):
45+
result = main.get_project_detail(attr, default_value, project_detail)
46+
print("✅ Response: ", result)
47+
# assert None
48+
assert result == default_value
49+
assert isinstance(result, str | int | tuple)

src/tests/test_main.py

Whitespace-only changes.

0 commit comments

Comments
 (0)