init-app 0.1.0__py3-none-any.whl
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.
- create_app/__init__.py +101 -0
- create_app/templates/__init__.py +0 -0
- create_app/templates/bottle/__init__.py +0 -0
- create_app/templates/bottle/minimal/__init__.py +0 -0
- create_app/templates/bottle/minimal/structure.py +60 -0
- create_app/templates/bottle/production/__init__.py +0 -0
- create_app/templates/bottle/production/structure.py +173 -0
- create_app/templates/django/__init__.py +0 -0
- create_app/templates/django/drf/__init__.py +0 -0
- create_app/templates/django/drf/structure.py +152 -0
- create_app/templates/django/minimal/__init__.py +0 -0
- create_app/templates/django/minimal/structure.py +140 -0
- create_app/templates/pyramid/__init__.py +0 -0
- create_app/templates/pyramid/minimal/__init__.py +0 -0
- create_app/templates/pyramid/minimal/structure.py +60 -0
- create_app/templates/pyramid/production/__init__.py +0 -0
- create_app/templates/pyramid/production/structure.py +169 -0
- create_app/templates/tornado/__init__.py +0 -0
- create_app/templates/tornado/minimal/__init__.py +0 -0
- create_app/templates/tornado/minimal/structure.py +70 -0
- create_app/templates/tornado/production/__init__.py +0 -0
- create_app/templates/tornado/production/structure.py +144 -0
- create_app/ui/__init__.py +0 -0
- create_app/ui/loader.py +62 -0
- create_app/ui/logger.py +47 -0
- create_app/ui/prompts.py +39 -0
- init_app-0.1.0.dist-info/METADATA +36 -0
- init_app-0.1.0.dist-info/RECORD +35 -0
- init_app-0.1.0.dist-info/WHEEL +5 -0
- init_app-0.1.0.dist-info/entry_points.txt +2 -0
- init_app-0.1.0.dist-info/licenses/LICENSE +0 -0
- init_app-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_full_matrix.py +83 -0
- tests/test_logic.py +44 -0
create_app/__init__.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
2
|
+
|
|
3
|
+
APP_NAME = "py-create"
|
|
4
|
+
APP_TAGLINE = "Python Backend Project Generator"
|
|
5
|
+
|
|
6
|
+
# ✅ Supported Frameworks
|
|
7
|
+
FRAMEWORKS = [
|
|
8
|
+
"Python",
|
|
9
|
+
"Flask",
|
|
10
|
+
"FastAPI",
|
|
11
|
+
"Django",
|
|
12
|
+
"Bottle",
|
|
13
|
+
"Falcon",
|
|
14
|
+
"Tornado",
|
|
15
|
+
"Pyramid",
|
|
16
|
+
"Sanic",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# ✅ Project Types for specialized Frameworks (Flask, FastAPI, etc.)
|
|
20
|
+
# We use "Standard" as the default high-quality starting point
|
|
21
|
+
PROJECT_STRUCTURES = ["Standard", "Production"]
|
|
22
|
+
|
|
23
|
+
STRUCTURE_DESCRIPTIONS = {
|
|
24
|
+
"Standard": "Clean, modern foundation with essential configurations",
|
|
25
|
+
"Production": "Enterprise-ready layout with tests, logs, and advanced scaling",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# ✅ Django Specifics
|
|
29
|
+
DJANGO_PROJECT_TYPES = ["Standard", "drf"]
|
|
30
|
+
|
|
31
|
+
DJANGO_DESCRIPTIONS = {
|
|
32
|
+
"Standard": "Full Django project with default configuration",
|
|
33
|
+
"drf": "Django project with REST Framework ready for API development",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ✅ Python Specifics (The "Swiss Army Knife" category)
|
|
37
|
+
PYTHON_PROJECT_TYPES = [
|
|
38
|
+
"Standard", # Basic clean setup
|
|
39
|
+
"CLI Application", # Command-line tool structure
|
|
40
|
+
"Library", # PyPI-ready package structure
|
|
41
|
+
"ML Labs", # Data Science (TF, PyHive, MLflow)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
PYTHON_DESCRIPTIONS = {
|
|
45
|
+
"Standard": "Refined universal foundation with a clean structure",
|
|
46
|
+
"CLI Application": "Professional CLI tool structure (Click/Rich integrated)",
|
|
47
|
+
"Library": "Standardized PyPI-ready package structure (PEP 621)",
|
|
48
|
+
"ML Labs": "Modern Data Science lab (TensorFlow, PyHive, MLflow tracking)",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# ✅ Environment & Database
|
|
52
|
+
VENV_OPTIONS = ["Yes (Recommended)", "No"]
|
|
53
|
+
|
|
54
|
+
DATABASE_OPTIONS = ["None", "SQLAlchemy", "PostgreSQL", "MySQL", "MongoDB"]
|
|
55
|
+
|
|
56
|
+
DATABASE_DESCRIPTIONS = {
|
|
57
|
+
"None": "No database integration",
|
|
58
|
+
"SQLAlchemy": "Database toolkit / ORM (flexible backend support)",
|
|
59
|
+
"PostgreSQL": "Powerful production-grade relational database",
|
|
60
|
+
"MySQL": "Popular relational database",
|
|
61
|
+
"MongoDB": "NoSQL document database",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ✅ Technical Configs
|
|
65
|
+
DEFAULT_PORTS = {
|
|
66
|
+
"Flask": "5000",
|
|
67
|
+
"FastAPI": "8000",
|
|
68
|
+
"Django": "8000",
|
|
69
|
+
"Sanic": "8000",
|
|
70
|
+
"Tornado": "8888",
|
|
71
|
+
"Falcon": "8000",
|
|
72
|
+
"Bottle": "8080",
|
|
73
|
+
"Pyramid": "6543",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# 🚀 LIFT CORE COMPONENTS (Public API)
|
|
77
|
+
from create_app.ui.loader import Spinner
|
|
78
|
+
from create_app.ui.prompts import ask_project_details
|
|
79
|
+
from create_app.cli.engine import ProjectEngine
|
|
80
|
+
from create_app.generator.generator import generate_project
|
|
81
|
+
|
|
82
|
+
# ✅ Public API Contract
|
|
83
|
+
__all__ = [
|
|
84
|
+
"APP_NAME",
|
|
85
|
+
"APP_TAGLINE",
|
|
86
|
+
"FRAMEWORKS",
|
|
87
|
+
"DJANGO_PROJECT_TYPES",
|
|
88
|
+
"DJANGO_DESCRIPTIONS",
|
|
89
|
+
"PROJECT_STRUCTURES",
|
|
90
|
+
"PYTHON_PROJECT_TYPES",
|
|
91
|
+
"PYTHON_DESCRIPTIONS",
|
|
92
|
+
"STRUCTURE_DESCRIPTIONS",
|
|
93
|
+
"VENV_OPTIONS",
|
|
94
|
+
"DATABASE_OPTIONS",
|
|
95
|
+
"DATABASE_DESCRIPTIONS",
|
|
96
|
+
"DEFAULT_PORTS",
|
|
97
|
+
"Spinner",
|
|
98
|
+
"ask_project_details",
|
|
99
|
+
"ProjectEngine",
|
|
100
|
+
"generate_project",
|
|
101
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from create_app.generator.renderer import render_template
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def generate(project_root: Path, context: dict):
|
|
6
|
+
"""
|
|
7
|
+
Bottle Minimal Structure Generator
|
|
8
|
+
|
|
9
|
+
Creates:
|
|
10
|
+
|
|
11
|
+
project/
|
|
12
|
+
├── app.py
|
|
13
|
+
├── __init__.py
|
|
14
|
+
├── requirements.txt
|
|
15
|
+
├── .env
|
|
16
|
+
├── README.md
|
|
17
|
+
└── .gitignore
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# ✅ Ensure project root exists
|
|
21
|
+
project_root.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
# ✅ Entry Point (Rendered from Template)
|
|
24
|
+
render_template(
|
|
25
|
+
"bottle/minimal/entry.py.tpl",
|
|
26
|
+
project_root / "app.py",
|
|
27
|
+
context,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# ✅ Common Files
|
|
31
|
+
|
|
32
|
+
render_template(
|
|
33
|
+
"common/__init__.py.tpl",
|
|
34
|
+
project_root / "__init__.py",
|
|
35
|
+
context,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
render_template(
|
|
39
|
+
"common/requirements.txt.tpl",
|
|
40
|
+
project_root / "requirements.txt",
|
|
41
|
+
context,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
render_template(
|
|
45
|
+
"common/.env.tpl",
|
|
46
|
+
project_root / ".env",
|
|
47
|
+
context,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
render_template(
|
|
51
|
+
"common/README.md.tpl",
|
|
52
|
+
project_root / "README.md",
|
|
53
|
+
context,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
render_template(
|
|
57
|
+
"common/gitignore.tpl",
|
|
58
|
+
project_root / ".gitignore",
|
|
59
|
+
context,
|
|
60
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
from create_app.generator.renderer import render_template
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# ✅ TEMPLATE ROOT 😈🔥
|
|
8
|
+
TEMPLATE_ROOT = Path(__file__).parents[2]
|
|
9
|
+
# → create_app/templates/
|
|
10
|
+
|
|
11
|
+
TEMPLATES_UI_DIR = TEMPLATE_ROOT / "common" / "template" / "bottle"
|
|
12
|
+
STATIC_UI_DIR = TEMPLATE_ROOT / "common" / "static"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ✅ Copy Shared UI 😈🔥
|
|
16
|
+
def copy_ui(project_root: Path):
|
|
17
|
+
|
|
18
|
+
shutil.copytree(
|
|
19
|
+
TEMPLATES_UI_DIR,
|
|
20
|
+
project_root / "views", # ✅ Bottle uses views
|
|
21
|
+
dirs_exist_ok=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
shutil.copytree(
|
|
25
|
+
STATIC_UI_DIR,
|
|
26
|
+
project_root / "static",
|
|
27
|
+
dirs_exist_ok=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate(project_root: Path, context: dict):
|
|
32
|
+
"""
|
|
33
|
+
Bottle Production Grade Generator 😈🔥
|
|
34
|
+
Clean layered architecture + Shared UI
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
project_root.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
|
|
39
|
+
# ✅ Core Directory Layout
|
|
40
|
+
folders = [
|
|
41
|
+
"config",
|
|
42
|
+
"routes",
|
|
43
|
+
"services",
|
|
44
|
+
"models",
|
|
45
|
+
"schemas",
|
|
46
|
+
"middleware",
|
|
47
|
+
"utils",
|
|
48
|
+
"logs",
|
|
49
|
+
"tests",
|
|
50
|
+
"views",
|
|
51
|
+
"static",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
for folder in folders:
|
|
55
|
+
(project_root / folder).mkdir(exist_ok=True)
|
|
56
|
+
|
|
57
|
+
# ✅ Static Subfolders 👍
|
|
58
|
+
for folder in ["css", "js", "assets"]:
|
|
59
|
+
(project_root / "static" / folder).mkdir(parents=True, exist_ok=True)
|
|
60
|
+
|
|
61
|
+
# ✅ Python Packages 👍
|
|
62
|
+
for folder in [
|
|
63
|
+
"config",
|
|
64
|
+
"routes",
|
|
65
|
+
"services",
|
|
66
|
+
"models",
|
|
67
|
+
"schemas",
|
|
68
|
+
"middleware",
|
|
69
|
+
"utils",
|
|
70
|
+
"tests",
|
|
71
|
+
]:
|
|
72
|
+
(project_root / folder / "__init__.py").touch()
|
|
73
|
+
|
|
74
|
+
# ✅ ENTRYPOINT 😈🔥
|
|
75
|
+
render_template(
|
|
76
|
+
"bottle/production/entry.py.tpl",
|
|
77
|
+
project_root / "app.py",
|
|
78
|
+
context,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# ✅ CONFIG FILE 👍
|
|
82
|
+
(project_root / "config" / "settings.py").write_text(
|
|
83
|
+
"""
|
|
84
|
+
import os
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Settings:
|
|
88
|
+
debug = os.getenv("DEBUG", "True") == "True"
|
|
89
|
+
host = os.getenv("HOST", "127.0.0.1")
|
|
90
|
+
port = int(os.getenv("PORT", 8080))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
settings = Settings()
|
|
94
|
+
""".strip() + "\n"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# ✅ ROUTES REGISTRY 👍
|
|
98
|
+
(project_root / "routes" / "__init__.py").write_text(
|
|
99
|
+
"""
|
|
100
|
+
from .health import register_health
|
|
101
|
+
from .auth import register_auth
|
|
102
|
+
from .api import register_api
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def register_routes(app):
|
|
106
|
+
register_health(app)
|
|
107
|
+
register_auth(app)
|
|
108
|
+
register_api(app)
|
|
109
|
+
""".strip() + "\n"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# ✅ ROUTES 👍
|
|
113
|
+
|
|
114
|
+
(project_root / "routes" / "health.py").write_text(
|
|
115
|
+
"""
|
|
116
|
+
from bottle import response
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def register_health(app):
|
|
120
|
+
|
|
121
|
+
@app.get("/health")
|
|
122
|
+
def health():
|
|
123
|
+
response.content_type = "application/json"
|
|
124
|
+
return {"status": "healthy"}
|
|
125
|
+
""".strip() + "\n"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
(project_root / "routes" / "auth.py").write_text(
|
|
129
|
+
"""
|
|
130
|
+
def register_auth(app):
|
|
131
|
+
|
|
132
|
+
@app.get("/auth")
|
|
133
|
+
def auth():
|
|
134
|
+
return {"message": "Auth route ready"}
|
|
135
|
+
""".strip() + "\n"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
(project_root / "routes" / "api.py").write_text(
|
|
139
|
+
"""
|
|
140
|
+
from bottle import template
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def register_api(app):
|
|
144
|
+
|
|
145
|
+
@app.get("/")
|
|
146
|
+
def index():
|
|
147
|
+
return template("index") # ✅ Loads Shared UI index.tpl
|
|
148
|
+
""".strip() + "\n"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# ✅ PLACEHOLDERS 👍
|
|
152
|
+
(project_root / "services" / "example_service.py").touch()
|
|
153
|
+
(project_root / "models" / "example_model.py").touch()
|
|
154
|
+
(project_root / "schemas" / "example_schema.py").touch()
|
|
155
|
+
(project_root / "middleware" / "example_middleware.py").touch()
|
|
156
|
+
(project_root / "utils" / "helpers.py").touch()
|
|
157
|
+
|
|
158
|
+
# ✅ LOG FILE 👍
|
|
159
|
+
(project_root / "logs" / "app.log").touch()
|
|
160
|
+
|
|
161
|
+
# ✅ TEST FILE 👍
|
|
162
|
+
(project_root / "tests" / "test_health.py").touch()
|
|
163
|
+
|
|
164
|
+
# ✅ ⭐ COPY SHARED UI ⭐ 😈🔥
|
|
165
|
+
copy_ui(project_root)
|
|
166
|
+
|
|
167
|
+
# 🔥 COMMON FILES 🔥
|
|
168
|
+
render_template("common/requirements.txt.tpl", project_root / "requirements.txt", context)
|
|
169
|
+
render_template("common/.env.tpl", project_root / ".env", context)
|
|
170
|
+
render_template("common/README.md.tpl", project_root / "README.md", context)
|
|
171
|
+
render_template("common/gitignore.tpl", project_root / ".gitignore", context)
|
|
172
|
+
|
|
173
|
+
return project_root
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from create_app.generator.renderer import render_template
|
|
7
|
+
|
|
8
|
+
# ✅ Ensure Django Installed 😈🔥
|
|
9
|
+
def ensure_django():
|
|
10
|
+
try:
|
|
11
|
+
subprocess.run(
|
|
12
|
+
[sys.executable, "-m", "django", "--version"],
|
|
13
|
+
stdout=subprocess.DEVNULL,
|
|
14
|
+
stderr=subprocess.DEVNULL,
|
|
15
|
+
check=True,
|
|
16
|
+
)
|
|
17
|
+
except subprocess.SubprocessError:
|
|
18
|
+
subprocess.run(
|
|
19
|
+
[sys.executable, "-m", "pip", "install", "django"],
|
|
20
|
+
check=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ✅ Patch settings.py 😈🔥
|
|
25
|
+
def patch_settings(settings_path: Path, context: dict):
|
|
26
|
+
if not settings_path.exists():
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
content = settings_path.read_text()
|
|
30
|
+
|
|
31
|
+
# ✅ Ensure import os exists 😈🔥
|
|
32
|
+
if "import os" not in content:
|
|
33
|
+
content = re.sub(
|
|
34
|
+
r"(from pathlib import Path.*\n)",
|
|
35
|
+
r"\1import os\n",
|
|
36
|
+
content,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# ✅ Replace SECRET KEY / DEBUG / HOSTS
|
|
40
|
+
secret_block = render_template(
|
|
41
|
+
"django/drf/secret.tpl",
|
|
42
|
+
None,
|
|
43
|
+
context,
|
|
44
|
+
raw=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
content = re.sub(
|
|
48
|
+
r"SECRET_KEY\s*=.*",
|
|
49
|
+
secret_block,
|
|
50
|
+
content,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# ✅ Inject Installed Apps
|
|
54
|
+
apps_block = render_template(
|
|
55
|
+
"django/drf/apps.py.tpl",
|
|
56
|
+
None,
|
|
57
|
+
context,
|
|
58
|
+
raw=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
pattern = r"INSTALLED_APPS\s*=\s*\[(.*?)\]"
|
|
62
|
+
match = re.search(pattern, content, re.DOTALL)
|
|
63
|
+
|
|
64
|
+
if not match:
|
|
65
|
+
raise RuntimeError("INSTALLED_APPS not found")
|
|
66
|
+
|
|
67
|
+
existing = match.group(1).strip()
|
|
68
|
+
updated = existing + "\n" + apps_block
|
|
69
|
+
|
|
70
|
+
content = re.sub(
|
|
71
|
+
pattern,
|
|
72
|
+
f"INSTALLED_APPS = [\n{updated}\n]",
|
|
73
|
+
content,
|
|
74
|
+
flags=re.DOTALL,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# ✅ Append DRF Config
|
|
78
|
+
drf_config = render_template(
|
|
79
|
+
"django/drf/rf.py.tpl",
|
|
80
|
+
None,
|
|
81
|
+
context,
|
|
82
|
+
raw=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
content += "\n\n" + drf_config
|
|
86
|
+
settings_path.write_text(content)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ✅ Overwrite urls.py 😈🔥
|
|
90
|
+
def overwrite_urls(urls_path: Path, context: dict):
|
|
91
|
+
if not urls_path.exists():
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
urls_content = render_template(
|
|
95
|
+
"django/drf/urls.tpl",
|
|
96
|
+
None,
|
|
97
|
+
context,
|
|
98
|
+
raw=True,
|
|
99
|
+
)
|
|
100
|
+
urls_path.write_text(urls_content)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ✅ MAIN GENERATOR 🚀
|
|
104
|
+
def generate(project_root: Path, context: dict):
|
|
105
|
+
project_name = context["project_name"]
|
|
106
|
+
app_name = context["app_name"]
|
|
107
|
+
base_path = project_root.parent
|
|
108
|
+
|
|
109
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
|
|
111
|
+
ensure_django()
|
|
112
|
+
|
|
113
|
+
# ✅ Step 1 — Create Project 😈🔥
|
|
114
|
+
subprocess.run(
|
|
115
|
+
[sys.executable, "-m", "django", "startproject", project_name],
|
|
116
|
+
cwd=base_path,
|
|
117
|
+
check=True,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
project_dir = base_path / project_name
|
|
121
|
+
|
|
122
|
+
# 🛡️ GUARD: Create folders if they don't exist (Fixes Pytest Mocks)
|
|
123
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
(project_dir / project_name).mkdir(exist_ok=True)
|
|
125
|
+
|
|
126
|
+
# ✅ Step 2 — Create App 👍
|
|
127
|
+
subprocess.run(
|
|
128
|
+
[sys.executable, "manage.py", "startapp", app_name],
|
|
129
|
+
cwd=project_dir,
|
|
130
|
+
check=True,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# 🛡️ GUARD: Ensure app folder exists for tests
|
|
134
|
+
(project_dir / app_name).mkdir(exist_ok=True)
|
|
135
|
+
|
|
136
|
+
# ✅ Step 3 — Patch Settings 😈🔥
|
|
137
|
+
settings_path = project_dir / project_name / "settings.py"
|
|
138
|
+
patch_settings(settings_path, context)
|
|
139
|
+
|
|
140
|
+
# ✅ Step 4 — Overwrite URLs 😈🔥
|
|
141
|
+
urls_path = project_dir / project_name / "urls.py"
|
|
142
|
+
overwrite_urls(urls_path, context)
|
|
143
|
+
|
|
144
|
+
# ✅ Step 5 — Common Files 🔥
|
|
145
|
+
render_template("common/requirements.txt.tpl", project_dir / "requirements.txt", context)
|
|
146
|
+
render_template("common/.env.tpl", project_dir / ".env", context)
|
|
147
|
+
render_template("common/README.md.tpl", project_dir / "README.md", context)
|
|
148
|
+
|
|
149
|
+
# Using the dot naming convention for consistency
|
|
150
|
+
render_template("common/gitignore.tpl", project_dir / ".gitignore", context)
|
|
151
|
+
|
|
152
|
+
return project_dir
|
|
File without changes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from create_app.generator.renderer import render_template
|
|
8
|
+
|
|
9
|
+
TEMPLATE_DIR = Path(__file__).parent
|
|
10
|
+
TEMPLATE_ROOT = Path(__file__).resolve().parents[2]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TEMPLATES_UI_DIR = TEMPLATE_ROOT / "common" / "template" / "django"
|
|
14
|
+
STATIC_UI_DIR = TEMPLATE_ROOT / "common" / "static"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_dependencies():
|
|
18
|
+
dependency_file = TEMPLATE_DIR / "requirements.txt"
|
|
19
|
+
return dependency_file.read_text().strip() if dependency_file.exists() else ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ✅ Copy Shared UI 😈🔥
|
|
23
|
+
def copy_ui(project_dir: Path):
|
|
24
|
+
"""Copies global templates and static files to the Django project root."""
|
|
25
|
+
templates_dest = project_dir / "templates"
|
|
26
|
+
static_dest = project_dir / "static"
|
|
27
|
+
|
|
28
|
+
if TEMPLATES_UI_DIR.exists():
|
|
29
|
+
shutil.copytree(TEMPLATES_UI_DIR, templates_dest, dirs_exist_ok=True)
|
|
30
|
+
else:
|
|
31
|
+
print(f"⚠ Shared Templates not found → {TEMPLATES_UI_DIR}")
|
|
32
|
+
|
|
33
|
+
if STATIC_UI_DIR.exists():
|
|
34
|
+
shutil.copytree(STATIC_UI_DIR, static_dest, dirs_exist_ok=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ✅ Patch Django settings.py 😈🔥
|
|
38
|
+
def patch_settings(settings_path: Path, context: dict):
|
|
39
|
+
if not settings_path.exists():
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
content = settings_path.read_text()
|
|
43
|
+
app_name = context["app_name"]
|
|
44
|
+
|
|
45
|
+
# ✅ Ensure import os
|
|
46
|
+
if "import os" not in content:
|
|
47
|
+
content = content.replace(
|
|
48
|
+
"from pathlib import Path",
|
|
49
|
+
"from pathlib import Path\nimport os",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# ✅ Configure Templates DIR to look at root/templates
|
|
53
|
+
content = re.sub(
|
|
54
|
+
r"'DIRS': \[(.*?)\]",
|
|
55
|
+
"'DIRS': [BASE_DIR / 'templates'],",
|
|
56
|
+
content,
|
|
57
|
+
flags=re.DOTALL,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# ✅ Register Installed App
|
|
61
|
+
apps_pattern = r"INSTALLED_APPS\s*=\s*\[(.*?)\]"
|
|
62
|
+
match = re.search(apps_pattern, content, re.DOTALL)
|
|
63
|
+
|
|
64
|
+
if match:
|
|
65
|
+
existing_apps = match.group(1)
|
|
66
|
+
if f"'{app_name}'" not in existing_apps:
|
|
67
|
+
updated_apps = existing_apps.strip() + f"\n '{app_name}',\n"
|
|
68
|
+
content = re.sub(
|
|
69
|
+
apps_pattern,
|
|
70
|
+
f"INSTALLED_APPS = [\n {updated_apps}]",
|
|
71
|
+
content,
|
|
72
|
+
flags=re.DOTALL,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
settings_path.write_text(content)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def generate(project_root: Path, context: dict):
|
|
79
|
+
"""
|
|
80
|
+
Django Standard Generator 😈🔥
|
|
81
|
+
Now with Shared UI and Test-Safety Guards
|
|
82
|
+
"""
|
|
83
|
+
project_name = context["project_name"]
|
|
84
|
+
app_name = context["app_name"]
|
|
85
|
+
base_path = project_root.parent
|
|
86
|
+
|
|
87
|
+
# ✅ Remove empty scaffold folder
|
|
88
|
+
if project_root.exists() and not any(project_root.iterdir()):
|
|
89
|
+
project_root.rmdir()
|
|
90
|
+
|
|
91
|
+
# ✅ 1. Create Django Project
|
|
92
|
+
subprocess.run(
|
|
93
|
+
[sys.executable, "-m", "django", "startproject", project_name],
|
|
94
|
+
cwd=base_path,
|
|
95
|
+
check=True,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 🛡️ GUARD: Ensure project_dir exists for the next steps (needed for MOCK tests)
|
|
99
|
+
project_dir = base_path / project_name
|
|
100
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
(project_dir / project_name).mkdir(exist_ok=True)
|
|
102
|
+
|
|
103
|
+
# ✅ 2. Create Django App
|
|
104
|
+
subprocess.run(
|
|
105
|
+
[sys.executable, "manage.py", "startapp", app_name],
|
|
106
|
+
cwd=project_dir,
|
|
107
|
+
check=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# 🛡️ GUARD: Ensure app folder exists before writing files
|
|
111
|
+
app_dir = project_dir / app_name
|
|
112
|
+
app_dir.mkdir(exist_ok=True)
|
|
113
|
+
|
|
114
|
+
# ✅ 3. Patch Settings
|
|
115
|
+
settings_path = project_dir / project_name / "settings.py"
|
|
116
|
+
if settings_path.exists():
|
|
117
|
+
patch_settings(settings_path, context)
|
|
118
|
+
|
|
119
|
+
# ✅ 4. Setup View logic
|
|
120
|
+
views_file = app_dir / "views.py"
|
|
121
|
+
views_file.write_text(f"""from django.shortcuts import render
|
|
122
|
+
|
|
123
|
+
def index(request):
|
|
124
|
+
return render(request, 'index.html')
|
|
125
|
+
""")
|
|
126
|
+
|
|
127
|
+
# ✅ 5. Copy Shared UI (index.html, CSS, JS) 😈🔥
|
|
128
|
+
copy_ui(project_dir)
|
|
129
|
+
|
|
130
|
+
# ✅ 6. Common Project Files
|
|
131
|
+
context.update({"dependencies": load_dependencies(), "entrypoint": "manage.py"})
|
|
132
|
+
|
|
133
|
+
render_template("common/requirements.txt.tpl", project_dir / "requirements.txt", context)
|
|
134
|
+
render_template("common/.env.tpl", project_dir / ".env", context)
|
|
135
|
+
render_template("common/README.md.tpl", project_dir / "README.md", context)
|
|
136
|
+
|
|
137
|
+
# Ensure this matches your physical file: .gitignore.tpl
|
|
138
|
+
render_template("common/gitignore.tpl", project_dir / ".gitignore", context)
|
|
139
|
+
|
|
140
|
+
return project_dir
|
|
File without changes
|
|
File without changes
|