pyapp-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyapp_cli-0.1.0/PKG-INFO +42 -0
- pyapp_cli-0.1.0/README.md +31 -0
- pyapp_cli-0.1.0/pyproject.toml +19 -0
- pyapp_cli-0.1.0/src/pyapp_cli/__init__.py +8 -0
- pyapp_cli-0.1.0/src/pyapp_cli/generator.py +209 -0
- pyapp_cli-0.1.0/src/pyapp_cli/logger.py +15 -0
- pyapp_cli-0.1.0/src/pyapp_cli/questions.py +47 -0
- pyapp_cli-0.1.0/src/pyapp_cli/schemas.py +12 -0
- pyapp_cli-0.1.0/src/pyapp_cli/templates/fastapi.py +8 -0
- pyapp_cli-0.1.0/src/pyapp_cli/templates/flask.py +8 -0
pyapp_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pyapp-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Dist: colorama>=0.4.6
|
|
6
|
+
Requires-Dist: fire>=0.7.1
|
|
7
|
+
Requires-Dist: inquirerpy>=0.3.4
|
|
8
|
+
Requires-Dist: pydantic>=2.12.5
|
|
9
|
+
Requires-Python: >=3.12, <4.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# PyApp CLI
|
|
13
|
+
|
|
14
|
+
## About
|
|
15
|
+
|
|
16
|
+
PyApp CLI is a powerful tool that makes python project setup painless. With this CLI you can easily scaffold FastAPI, Flask or Django project with all necessary dependencies.
|
|
17
|
+
|
|
18
|
+
## Supported platforms
|
|
19
|
+
|
|
20
|
+
The CLI can be run on Ubuntu Linux, but it's not guaranteed the CLI will work on other platforms.
|
|
21
|
+
|
|
22
|
+
## Get started
|
|
23
|
+
|
|
24
|
+
Before setting up new project, install Python and appropriate package manager you'll use for your project. PyAPP CLI supports following package managers:
|
|
25
|
+
|
|
26
|
+
- pip
|
|
27
|
+
- poetry
|
|
28
|
+
- uv
|
|
29
|
+
|
|
30
|
+
Use `main.py init` command to run the CLI:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python main.py init # python interpreter
|
|
34
|
+
poetry run python main.py init # poetry
|
|
35
|
+
uv run main.py init # uv
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Alertnatively, you can use python3 prefix when running command.
|
|
39
|
+
|
|
40
|
+
## Contributing
|
|
41
|
+
|
|
42
|
+
Feel free to open an issue if you have found a bug, have a feature request or you want to expand list of available libraries/frameworks/package managers. You also can open PR if you wish.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# PyApp CLI
|
|
2
|
+
|
|
3
|
+
## About
|
|
4
|
+
|
|
5
|
+
PyApp CLI is a powerful tool that makes python project setup painless. With this CLI you can easily scaffold FastAPI, Flask or Django project with all necessary dependencies.
|
|
6
|
+
|
|
7
|
+
## Supported platforms
|
|
8
|
+
|
|
9
|
+
The CLI can be run on Ubuntu Linux, but it's not guaranteed the CLI will work on other platforms.
|
|
10
|
+
|
|
11
|
+
## Get started
|
|
12
|
+
|
|
13
|
+
Before setting up new project, install Python and appropriate package manager you'll use for your project. PyAPP CLI supports following package managers:
|
|
14
|
+
|
|
15
|
+
- pip
|
|
16
|
+
- poetry
|
|
17
|
+
- uv
|
|
18
|
+
|
|
19
|
+
Use `main.py init` command to run the CLI:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python main.py init # python interpreter
|
|
23
|
+
poetry run python main.py init # poetry
|
|
24
|
+
uv run main.py init # uv
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Alertnatively, you can use python3 prefix when running command.
|
|
28
|
+
|
|
29
|
+
## Contributing
|
|
30
|
+
|
|
31
|
+
Feel free to open an issue if you have found a bug, have a feature request or you want to expand list of available libraries/frameworks/package managers. You also can open PR if you wish.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyapp-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12,<4.0"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"colorama>=0.4.6",
|
|
9
|
+
"fire>=0.7.1",
|
|
10
|
+
"inquirerpy>=0.3.4",
|
|
11
|
+
"pydantic>=2.12.5",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
pyapp = "pyapp_cli:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.10.12,<0.11.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from InquirerPy import prompt
|
|
9
|
+
|
|
10
|
+
from .logger import Logger
|
|
11
|
+
from .questions import questions
|
|
12
|
+
from .schemas import Answers
|
|
13
|
+
|
|
14
|
+
venv_dir = ".venv"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProjectGenerator:
|
|
18
|
+
# frameworks without built-in CLI for scaffolding the project
|
|
19
|
+
_framework_packages = set(["FastAPI", "Flask"])
|
|
20
|
+
_templates_dir = os.path.abspath("templates")
|
|
21
|
+
|
|
22
|
+
def __init__(self, logger: Logger) -> None:
|
|
23
|
+
self._logger = logger
|
|
24
|
+
|
|
25
|
+
def init(self):
|
|
26
|
+
raw_answers = prompt(questions)
|
|
27
|
+
answers = Answers.model_validate(raw_answers)
|
|
28
|
+
|
|
29
|
+
self._create_project_folder(answers.project_path, answers.source_folder)
|
|
30
|
+
|
|
31
|
+
if answers.package_manager == "pip":
|
|
32
|
+
self._ensure_pip_installation()
|
|
33
|
+
elif answers.package_manager == "poetry":
|
|
34
|
+
self._ensure_poetry_installation()
|
|
35
|
+
elif answers.package_manager == "uv":
|
|
36
|
+
self._ensure_uv_installation()
|
|
37
|
+
|
|
38
|
+
if answers.framework == "None" or answers.framework in self._framework_packages:
|
|
39
|
+
self._create_main_file(answers.source_folder, answers.framework)
|
|
40
|
+
|
|
41
|
+
dependencies = answers.libraries
|
|
42
|
+
if answers.framework != "None":
|
|
43
|
+
dependencies.append(self._framework_id(answers.framework))
|
|
44
|
+
|
|
45
|
+
if answers.package_manager == "pip":
|
|
46
|
+
self._pip_setup_project(dependencies)
|
|
47
|
+
elif answers.package_manager == "poetry":
|
|
48
|
+
self._poetry_setup_project(dependencies)
|
|
49
|
+
elif answers.package_manager == "uv":
|
|
50
|
+
self._uv_setup_project(dependencies, answers.python_version)
|
|
51
|
+
|
|
52
|
+
if answers.framework == "Django":
|
|
53
|
+
self._django_setup_project(answers.package_manager, answers.source_folder)
|
|
54
|
+
|
|
55
|
+
self._logger.success("Finished! Enjoy the project :)")
|
|
56
|
+
|
|
57
|
+
def _framework_id(self, framework: str) -> str:
|
|
58
|
+
return framework.lower()
|
|
59
|
+
|
|
60
|
+
def _missing_dependency_message(self, dependency: str) -> str:
|
|
61
|
+
return (
|
|
62
|
+
f"Can't detect installed {dependency}.\n"
|
|
63
|
+
f"Ensure {dependency} is installed and then run the script again."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _create_project_folder(self, project_path: str, source_folder: str) -> None:
|
|
67
|
+
Path(project_path).mkdir(exist_ok=True)
|
|
68
|
+
if source_folder != "root":
|
|
69
|
+
Path(project_path, source_folder).mkdir(exist_ok=True)
|
|
70
|
+
os.chdir(project_path)
|
|
71
|
+
self._logger.log(f"Created folder '{project_path}'")
|
|
72
|
+
|
|
73
|
+
def _ensure_pip_installation(self) -> None:
|
|
74
|
+
pip_exists = shutil.which("pip") is not None
|
|
75
|
+
if pip_exists:
|
|
76
|
+
self._logger.success("Detected pip")
|
|
77
|
+
else:
|
|
78
|
+
self._logger.error(self._missing_dependency_message("pip"))
|
|
79
|
+
exit(127)
|
|
80
|
+
|
|
81
|
+
def _ensure_poetry_installation(self) -> None:
|
|
82
|
+
poetry_exists = shutil.which("poetry") is not None
|
|
83
|
+
if poetry_exists:
|
|
84
|
+
self._logger.success("Detected Poetry")
|
|
85
|
+
else:
|
|
86
|
+
self._logger.error(self._missing_dependency_message("Poetry"))
|
|
87
|
+
exit(127)
|
|
88
|
+
|
|
89
|
+
def _ensure_uv_installation(self) -> None:
|
|
90
|
+
uv_exists = shutil.which("uv") is not None
|
|
91
|
+
if uv_exists:
|
|
92
|
+
self._logger.success("Detected uv")
|
|
93
|
+
else:
|
|
94
|
+
self._logger.error(self._missing_dependency_message("uv"))
|
|
95
|
+
exit(127)
|
|
96
|
+
|
|
97
|
+
def _create_main_file(self, source_folder: str, framework: str) -> None:
|
|
98
|
+
python_file = None
|
|
99
|
+
if source_folder == "root":
|
|
100
|
+
python_file = Path("main.py")
|
|
101
|
+
else:
|
|
102
|
+
python_file = Path(source_folder, "main.py")
|
|
103
|
+
python_file.parent.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
self._logger.log("Added main.py")
|
|
105
|
+
|
|
106
|
+
project_template = None
|
|
107
|
+
try:
|
|
108
|
+
template_path = os.path.join(
|
|
109
|
+
self._templates_dir, f"{self._framework_id(framework)}.py"
|
|
110
|
+
)
|
|
111
|
+
with open(template_path, "r") as template_file:
|
|
112
|
+
project_template = template_file.read()
|
|
113
|
+
self._logger.log(f"Applied {self._framework_id(framework)} template")
|
|
114
|
+
except FileNotFoundError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
with open(python_file, "w") as f:
|
|
118
|
+
if project_template is not None:
|
|
119
|
+
f.write(project_template)
|
|
120
|
+
|
|
121
|
+
def _pip_setup_project(self, dependencies: list[str]) -> None:
|
|
122
|
+
self._logger.log("Creating virtual environment...")
|
|
123
|
+
subprocess.check_call(
|
|
124
|
+
[
|
|
125
|
+
sys.executable,
|
|
126
|
+
"-m",
|
|
127
|
+
"venv",
|
|
128
|
+
os.path.join(venv_dir),
|
|
129
|
+
],
|
|
130
|
+
stdout=subprocess.DEVNULL,
|
|
131
|
+
stderr=subprocess.DEVNULL,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if len(dependencies) > 0:
|
|
135
|
+
self._logger.log("Installing dependencies...")
|
|
136
|
+
python_executable = os.path.abspath(
|
|
137
|
+
os.path.join(venv_dir, "bin", "python3")
|
|
138
|
+
)
|
|
139
|
+
subprocess.check_call(
|
|
140
|
+
[python_executable, "-m", "pip", "install", *dependencies],
|
|
141
|
+
)
|
|
142
|
+
result = subprocess.run(
|
|
143
|
+
[python_executable, "-m", "pip", "freeze"],
|
|
144
|
+
check=True,
|
|
145
|
+
capture_output=True,
|
|
146
|
+
text=True,
|
|
147
|
+
)
|
|
148
|
+
with open("requirements.txt", "w") as f:
|
|
149
|
+
f.write(result.stdout)
|
|
150
|
+
self._logger.success("Saved dependencies to requirements.txt")
|
|
151
|
+
|
|
152
|
+
def _poetry_setup_project(self, dependencies: list[str]) -> None:
|
|
153
|
+
subprocess.check_call(
|
|
154
|
+
["poetry", "init", "-n"],
|
|
155
|
+
stdout=subprocess.DEVNULL,
|
|
156
|
+
stderr=subprocess.DEVNULL,
|
|
157
|
+
)
|
|
158
|
+
self._logger.log("Initialized poetry project")
|
|
159
|
+
|
|
160
|
+
if len(dependencies) > 0:
|
|
161
|
+
self._logger.log("Installing dependencies...")
|
|
162
|
+
custom_env = os.environ.copy()
|
|
163
|
+
custom_env["POETRY_VIRTUALENVS_IN_PROJECT"] = "true"
|
|
164
|
+
custom_env["VIRTUAL_ENV"] = os.path.abspath(venv_dir)
|
|
165
|
+
subprocess.check_call(
|
|
166
|
+
[
|
|
167
|
+
"poetry",
|
|
168
|
+
"add",
|
|
169
|
+
*dependencies,
|
|
170
|
+
],
|
|
171
|
+
env=custom_env,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _uv_setup_project(self, dependencies: list[str], python_version: str) -> None:
|
|
175
|
+
subprocess.check_call(
|
|
176
|
+
["uv", "init", "--bare", "--no-workspace"],
|
|
177
|
+
stdout=subprocess.DEVNULL,
|
|
178
|
+
stderr=subprocess.DEVNULL,
|
|
179
|
+
)
|
|
180
|
+
self._logger.log("Initialized uv project")
|
|
181
|
+
|
|
182
|
+
with open(".python-version", "w") as f:
|
|
183
|
+
f.write(python_version)
|
|
184
|
+
|
|
185
|
+
if len(dependencies) > 0:
|
|
186
|
+
self._logger.log("Installing dependencies...")
|
|
187
|
+
subprocess.check_call(
|
|
188
|
+
[
|
|
189
|
+
"uv",
|
|
190
|
+
"add",
|
|
191
|
+
*dependencies,
|
|
192
|
+
],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _django_setup_project(
|
|
196
|
+
self, package_manager: Literal["pip", "poetry", "uv"], source_folder: str
|
|
197
|
+
) -> None:
|
|
198
|
+
python_executable = os.path.abspath(os.path.join(venv_dir, "bin", "python3"))
|
|
199
|
+
django_util = [python_executable, "-m", "django"]
|
|
200
|
+
if package_manager == "uv":
|
|
201
|
+
django_util = ["uv", "run", "django-admin"]
|
|
202
|
+
self._logger.log("Initialized Django project")
|
|
203
|
+
|
|
204
|
+
if source_folder != "root":
|
|
205
|
+
os.chdir(source_folder)
|
|
206
|
+
|
|
207
|
+
subprocess.check_call(
|
|
208
|
+
[*django_util, "startproject", "mysite", "."],
|
|
209
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from colorama import Fore, Style, init
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Logger:
|
|
5
|
+
def __init__(self) -> None:
|
|
6
|
+
init(autoreset=True)
|
|
7
|
+
|
|
8
|
+
def log(self, message: str) -> None:
|
|
9
|
+
print(Style.DIM + message)
|
|
10
|
+
|
|
11
|
+
def success(self, message: str) -> None:
|
|
12
|
+
print(Fore.GREEN + message)
|
|
13
|
+
|
|
14
|
+
def error(self, message: str) -> None:
|
|
15
|
+
print(Fore.RED + message)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
questions = [
|
|
2
|
+
{
|
|
3
|
+
"type": "input",
|
|
4
|
+
"name": "project_path",
|
|
5
|
+
"message": "Enter the project path",
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"type": "list",
|
|
9
|
+
"name": "package_manager",
|
|
10
|
+
"message": "Choose the package manager",
|
|
11
|
+
"choices": ["pip", "poetry", "uv"],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "input",
|
|
15
|
+
"name": "python_version",
|
|
16
|
+
"message": "Enter Python version",
|
|
17
|
+
"default": "3.12",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "list",
|
|
21
|
+
"name": "source_folder",
|
|
22
|
+
"message": "Choose the source code folder (root - project folder)",
|
|
23
|
+
"choices": ["root", "src", "app"],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "list",
|
|
27
|
+
"name": "framework",
|
|
28
|
+
"message": "Choose the framework",
|
|
29
|
+
"choices": ["None", "FastAPI", "Flask", "Django"],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "checkbox",
|
|
33
|
+
"name": "libraries",
|
|
34
|
+
"message": "Choose the libraries",
|
|
35
|
+
"choices": [
|
|
36
|
+
"gunicorn",
|
|
37
|
+
"uvicorn",
|
|
38
|
+
"sqlalchemy",
|
|
39
|
+
"firebase-admin",
|
|
40
|
+
"pydantic",
|
|
41
|
+
"pydantic-settings",
|
|
42
|
+
"pytest",
|
|
43
|
+
"pytest-asyncio",
|
|
44
|
+
"python-dotenv",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Answers(BaseModel):
|
|
7
|
+
project_path: str
|
|
8
|
+
package_manager: Literal["pip", "poetry", "uv"]
|
|
9
|
+
python_version: str
|
|
10
|
+
source_folder: Literal["root", "src", "app"]
|
|
11
|
+
framework: Literal["None", "FastAPI", "Flask", "Django"]
|
|
12
|
+
libraries: list[str]
|