FastAPI-fastkit 0.1.1__py3-none-any.whl → 1.0.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.
Files changed (171) hide show
  1. fastapi_fastkit/__init__.py +1 -1
  2. fastapi_fastkit/backend/main.py +363 -72
  3. fastapi_fastkit/backend/transducer.py +36 -0
  4. fastapi_fastkit/cli.py +230 -43
  5. fastapi_fastkit/core/settings.py +12 -2
  6. fastapi_fastkit/fastapi_project_template/README.md +20 -1
  7. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.env-tpl +1 -0
  8. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.gitignore-tpl +28 -189
  9. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +102 -12
  10. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +27 -38
  11. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/format.sh-tpl +5 -0
  12. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/lint.sh-tpl +5 -0
  13. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/run-server.sh-tpl +8 -0
  14. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/test.sh-tpl +6 -0
  15. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.cfg-tpl +7 -6
  16. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +13 -23
  17. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/__init__.py-tpl +0 -88
  18. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/api.py-tpl +9 -0
  19. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/routes/items.py-tpl +57 -0
  20. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/config.py-tpl +64 -0
  21. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/items.py-tpl +21 -0
  22. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/main.py-tpl +26 -0
  23. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/mocks/mock_items.json-tpl +8 -0
  24. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/__init__.py-tpl +0 -48
  25. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/items.py-tpl +22 -0
  26. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/conftest.py-tpl +33 -13
  27. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/test_items.py-tpl +48 -0
  28. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.env-tpl +1 -0
  29. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.gitignore-tpl +191 -0
  30. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/README.md-tpl +156 -0
  31. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +36 -0
  32. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/format.sh-tpl +5 -0
  33. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/lint.sh-tpl +5 -0
  34. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/run-server.sh-tpl +8 -0
  35. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/test.sh-tpl +6 -0
  36. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.cfg-tpl +12 -0
  37. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.py-tpl +27 -0
  38. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/api.py-tpl +9 -0
  39. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/__init__.py-tpl +0 -0
  40. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/items.py-tpl +172 -0
  41. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/__init__.py-tpl +0 -0
  42. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/config.py-tpl +64 -0
  43. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/__init__.py-tpl +0 -0
  44. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/items.py-tpl +21 -0
  45. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/__init__.py-tpl +0 -0
  46. fastapi_fastkit/fastapi_project_template/{fastapi-async-crud → fastapi-custom-response}/src/helper/exceptions.py-tpl +12 -3
  47. fastapi_fastkit/fastapi_project_template/{fastapi-async-crud → fastapi-custom-response}/src/helper/pagination.py-tpl +3 -2
  48. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/main.py-tpl +39 -0
  49. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/mocks/__init__.py-tpl +0 -0
  50. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/mocks/mock_items.json-tpl +8 -0
  51. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/__init__.py-tpl +0 -0
  52. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/base.py-tpl +49 -0
  53. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/items.py-tpl +22 -0
  54. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/utils/__init__.py-tpl +0 -0
  55. fastapi_fastkit/fastapi_project_template/{fastapi-async-crud → fastapi-custom-response}/src/utils/documents.py-tpl +1 -1
  56. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/__init__.py-tpl +0 -0
  57. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/conftest.py-tpl +42 -0
  58. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/test_items.py-tpl +61 -0
  59. fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +85 -9
  60. fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/run-server.sh-tpl +8 -0
  61. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +1 -1
  62. fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_items.json-tpl +2 -2
  63. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +15 -0
  64. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +6 -24
  65. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.env-tpl +1 -0
  66. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.gitignore-tpl +30 -0
  67. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/Dockerfile-tpl +23 -0
  68. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +119 -0
  69. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +36 -0
  70. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/format.sh-tpl +5 -0
  71. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/lint.sh-tpl +5 -0
  72. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/run-server.sh-tpl +8 -0
  73. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/test.sh-tpl +6 -0
  74. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.cfg-tpl +13 -0
  75. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.py-tpl +22 -0
  76. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/__init__.py-tpl +0 -0
  77. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/__init__.py-tpl +0 -0
  78. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/api.py-tpl +6 -0
  79. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/__init__.py-tpl +0 -0
  80. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/items.py-tpl +54 -0
  81. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/__init__.py-tpl +0 -0
  82. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/config.py-tpl +64 -0
  83. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/__init__.py-tpl +0 -0
  84. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/items.py-tpl +15 -0
  85. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/main.py-tpl +26 -0
  86. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/mocks/__init__.py-tpl +0 -0
  87. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/mocks/mock_items.json-tpl +8 -0
  88. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/__init__.py-tpl +0 -0
  89. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/items.py-tpl +19 -0
  90. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/__init__.py-tpl +0 -0
  91. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/conftest.py-tpl +30 -0
  92. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/test_items.py-tpl +44 -0
  93. fastapi_fastkit/fastapi_project_template/fastapi-empty/.env-tpl +1 -0
  94. fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.py-tpl +22 -0
  95. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/__init__.py-tpl +0 -0
  96. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/__init__.py-tpl +0 -0
  97. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/config.py-tpl +75 -0
  98. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/main.py-tpl +36 -0
  99. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.env-tpl +9 -0
  100. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.gitignore-tpl +30 -0
  101. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/Dockerfile-tpl +23 -0
  102. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/README.md-tpl +144 -0
  103. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/alembic.ini-tpl +119 -0
  104. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/docker-compose.yml-tpl +44 -0
  105. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +41 -0
  106. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/format.sh-tpl +5 -0
  107. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/lint.sh-tpl +5 -0
  108. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/pre-start.sh-tpl +16 -0
  109. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/run-server.sh-tpl +8 -0
  110. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/test.sh-tpl +10 -0
  111. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.cfg-tpl +13 -0
  112. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.py-tpl +25 -0
  113. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/__init__.py-tpl +0 -0
  114. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/README-tpl +1 -0
  115. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/env.py-tpl +85 -0
  116. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/script.py.mako-tpl +26 -0
  117. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/versions/bedcdc35b64a_first_alembic.py-tpl +37 -0
  118. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/__init__.py-tpl +0 -0
  119. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/api.py-tpl +11 -0
  120. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/deps.py-tpl +19 -0
  121. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/__init__.py-tpl +0 -0
  122. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/items.py-tpl +109 -0
  123. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/__init__.py-tpl +0 -0
  124. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/config.py-tpl +88 -0
  125. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/db.py-tpl +53 -0
  126. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/__init__.py-tpl +0 -0
  127. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/items.py-tpl +102 -0
  128. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/main.py-tpl +45 -0
  129. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/__init__.py-tpl +0 -0
  130. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/items.py-tpl +54 -0
  131. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/__init__.py-tpl +0 -0
  132. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/backend_pre_start.py-tpl +39 -0
  133. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/init_data.py-tpl +23 -0
  134. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/tests_pre_start.py-tpl +39 -0
  135. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/__init__.py-tpl +0 -0
  136. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/conftest.py-tpl +63 -0
  137. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/test_items.py-tpl +44 -0
  138. fastapi_fastkit/fastapi_project_template/modules/api/__init__.py-tpl +0 -0
  139. fastapi_fastkit/fastapi_project_template/modules/api/routes/__init__.py-tpl +0 -0
  140. fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl +64 -0
  141. fastapi_fastkit/fastapi_project_template/modules/crud/__init__.py-tpl +0 -0
  142. fastapi_fastkit/fastapi_project_template/modules/crud/new_route.py-tpl +29 -0
  143. fastapi_fastkit/fastapi_project_template/modules/schemas/__init__.py-tpl +0 -0
  144. fastapi_fastkit/fastapi_project_template/modules/schemas/new_route.py-tpl +23 -0
  145. fastapi_fastkit/utils/main.py +51 -5
  146. fastapi_fastkit-1.0.0.dist-info/METADATA +208 -0
  147. fastapi_fastkit-1.0.0.dist-info/RECORD +189 -0
  148. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.env.test-tpl +0 -3
  149. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/main.py-tpl +0 -21
  150. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/script/run-server.sh-tpl +0 -2
  151. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/script/run-test.sh-tpl +0 -2
  152. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/settings.py-tpl +0 -96
  153. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/_base.py-tpl +0 -92
  154. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/user.py-tpl +0 -44
  155. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/helper/global_data.py-tpl +0 -33
  156. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/helper/logging.py-tpl +0 -25
  157. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/mocks/mock_users.json-tpl +0 -17
  158. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/router/__init__.py-tpl +0 -48
  159. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/router/user.py-tpl +0 -126
  160. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/user.py-tpl +0 -81
  161. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/templates/index.html-tpl +0 -27
  162. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/routes/test_user.py-tpl +0 -112
  163. fastapi_fastkit-0.1.1.dist-info/METADATA +0 -173
  164. fastapi_fastkit-0.1.1.dist-info/RECORD +0 -84
  165. /fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/{helper → api}/__init__.py-tpl +0 -0
  166. /fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/{utils → api/routes}/__init__.py-tpl +0 -0
  167. /fastapi_fastkit/fastapi_project_template/{fastapi-async-crud/tests/routes → fastapi-custom-response/src}/__init__.py-tpl +0 -0
  168. /fastapi_fastkit/fastapi_project_template/{fastapi-customized-response/README.md-tpl → fastapi-custom-response/src/api/__init__.py-tpl} +0 -0
  169. {fastapi_fastkit-0.1.1.dist-info → fastapi_fastkit-1.0.0.dist-info}/WHEEL +0 -0
  170. {fastapi_fastkit-0.1.1.dist-info → fastapi_fastkit-1.0.0.dist-info}/entry_points.txt +0 -0
  171. {fastapi_fastkit-0.1.1.dist-info → fastapi_fastkit-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
1
- __version__ = 'v0.1.1'
1
+ __version__ = 'v1.0.0'
@@ -5,17 +5,32 @@
5
5
  # --------------------------------------------------------------------------
6
6
  import os
7
7
  import subprocess
8
+ from typing import Dict, List
8
9
 
9
10
  from fastapi_fastkit import console
11
+ from fastapi_fastkit.backend.transducer import copy_and_convert_template_file
10
12
  from fastapi_fastkit.core.exceptions import BackendExceptions, TemplateExceptions
11
13
  from fastapi_fastkit.core.settings import settings
12
- from fastapi_fastkit.utils.main import print_error, print_info
14
+ from fastapi_fastkit.utils.main import (
15
+ handle_exception,
16
+ print_error,
17
+ print_info,
18
+ print_success,
19
+ print_warning,
20
+ )
13
21
 
22
+ # ------------------------------------------------------------
23
+ # Template Discovery Functions
24
+ # ------------------------------------------------------------
14
25
 
15
- def find_template_core_modules(project_dir: str) -> dict[str, str]:
26
+
27
+ def find_template_core_modules(project_dir: str) -> Dict[str, str]:
16
28
  """
17
29
  Find core module files in the template project structure.
18
30
  Returns a dictionary with paths to main.py, setup.py, and config files.
31
+
32
+ :param project_dir: Path to the project directory
33
+ :return: Dictionary with paths to core modules
19
34
  """
20
35
  core_modules = {"main": "", "setup": "", "config": ""}
21
36
  template_config = settings.TEMPLATE_PATHS["config"]
@@ -34,7 +49,7 @@ def find_template_core_modules(project_dir: str) -> dict[str, str]:
34
49
  core_modules["setup"] = full_path
35
50
  break
36
51
 
37
- # Find config files (settings.py or config.py)
52
+ # Find config files
38
53
  if isinstance(template_config, dict):
39
54
  for config_file in template_config.get("files", []):
40
55
  for base_path in template_config.get("paths", []):
@@ -48,6 +63,48 @@ def find_template_core_modules(project_dir: str) -> dict[str, str]:
48
63
  return core_modules
49
64
 
50
65
 
66
+ def read_template_stack(template_path: str) -> List[str]:
67
+ """
68
+ Read the install_requires from setup.py-tpl in the template directory.
69
+ Returns a list of required packages.
70
+
71
+ :param template_path: Path to the template directory
72
+ :return: List of required packages
73
+ """
74
+ setup_path = os.path.join(template_path, "setup.py-tpl")
75
+ if not os.path.exists(setup_path):
76
+ return []
77
+
78
+ try:
79
+ with open(setup_path, "r") as f:
80
+ content = f.read()
81
+ # Find the install_requires section using proper string matching
82
+ if "install_requires: list[str] = [" in content:
83
+ start_idx = content.find("install_requires: list[str] = [") + len(
84
+ "install_requires: list[str] = ["
85
+ )
86
+ end_idx = content.find("]", start_idx)
87
+ if start_idx != -1 and end_idx != -1:
88
+ deps_str = content[start_idx:end_idx]
89
+ # Clean and parse the dependencies
90
+ deps = [
91
+ dep.strip().strip("'").strip('"')
92
+ for dep in deps_str.split(",")
93
+ if dep.strip() and not dep.isspace()
94
+ ]
95
+ return [dep for dep in deps if dep] # Remove empty strings
96
+ except Exception as e:
97
+ handle_exception(e, "Error reading template dependencies")
98
+ return []
99
+
100
+ return []
101
+
102
+
103
+ # ------------------------------------------------------------
104
+ # Project Setup Functions
105
+ # ------------------------------------------------------------
106
+
107
+
51
108
  def inject_project_metadata(
52
109
  target_dir: str,
53
110
  project_name: str,
@@ -60,42 +117,54 @@ def inject_project_metadata(
60
117
  """
61
118
  try:
62
119
  core_modules = find_template_core_modules(target_dir)
120
+ setup_py = core_modules.get("setup", "")
121
+ config_py = core_modules.get("config", "")
63
122
 
64
- # Inject metadata to main.py
65
- if core_modules["main"]:
66
- with open(core_modules["main"], "r+") as f:
123
+ if setup_py and os.path.exists(setup_py):
124
+ with open(setup_py, "r") as f:
67
125
  content = f.read()
68
- content = content.replace("app_title", f'"{project_name}"')
69
- content = content.replace("app_description", f'"{description}"')
70
- f.seek(0)
71
- f.write(content)
72
- f.truncate()
73
126
 
74
- # Inject metadata to setup.py
75
- if core_modules["setup"]:
76
- with open(core_modules["setup"], "r+") as f:
77
- content = f.read()
78
- content = content.replace("<project_name>", project_name, 1)
79
- content = content.replace("<description>", description, 1)
80
- content = content.replace("<author>", author, 1)
81
- content = content.replace("<author_email>", author_email, 1)
82
- f.seek(0)
127
+ # Replace placeholders
128
+ content = content.replace("<project_name>", project_name)
129
+ content = content.replace("<author>", author)
130
+ content = content.replace("<author_email>", author_email)
131
+ content = content.replace("<description>", description)
132
+
133
+ with open(setup_py, "w") as f:
83
134
  f.write(content)
84
- f.truncate()
135
+ print_info("Injected metadata into setup.py")
85
136
 
86
- # Inject metadata to config files
87
- if core_modules["config"]:
88
- with open(core_modules["config"], "r+") as f:
137
+ if config_py and os.path.exists(config_py):
138
+ with open(config_py, "r") as f:
89
139
  content = f.read()
90
- content = content.replace("<project_name>", project_name)
91
- content = content.replace("<description>", description)
92
- f.seek(0)
140
+
141
+ content = content.replace("<project_name>", project_name)
142
+
143
+ with open(config_py, "w") as f:
93
144
  f.write(content)
94
- f.truncate()
145
+ print_info("Injected metadata into config file")
146
+
147
+ # Try to find the readme file
148
+ readme_files = ["README.md", "Readme.md", "readme.md"]
149
+ for readme in readme_files:
150
+ readme_path = os.path.join(target_dir, readme)
151
+ if os.path.exists(readme_path):
152
+ with open(readme_path, "r") as f:
153
+ content = f.read()
154
+
155
+ content = content.replace(
156
+ "{FASTAPI TEMPLATE PROJECT NAME}", project_name
157
+ )
158
+ content = content.replace("{fill here}", author)
159
+
160
+ with open(readme_path, "w") as f:
161
+ f.write(content)
162
+ print_info("Injected metadata into README.md")
163
+ break
95
164
 
96
165
  except Exception as e:
97
- print_error(f"Error during metadata injection: {e}")
98
- raise TemplateExceptions("Failed to inject metadata")
166
+ handle_exception(e, "Failed to inject project metadata")
167
+ raise BackendExceptions("Failed to inject project metadata")
99
168
 
100
169
 
101
170
  def create_venv(project_dir: str) -> str:
@@ -125,67 +194,289 @@ def create_venv(project_dir: str) -> str:
125
194
 
126
195
 
127
196
  def install_dependencies(project_dir: str, venv_path: str) -> None:
128
- """Install project dependencies in the virtual environment."""
197
+ """
198
+ Install project dependencies into the virtual environment.
199
+
200
+ :param project_dir: Path to the project directory
201
+ :param venv_path: Path to the virtual environment
202
+ :return: None
203
+ """
129
204
  try:
205
+ if not os.path.exists(venv_path):
206
+ print_error("Virtual environment does not exist. Creating it first.")
207
+ venv_path = create_venv(project_dir)
208
+ if not venv_path:
209
+ raise BackendExceptions("Failed to create virtual environment")
210
+
211
+ requirements_path = os.path.join(project_dir, "requirements.txt")
212
+ if not os.path.exists(requirements_path):
213
+ print_error(f"Requirements file not found at {requirements_path}")
214
+ raise BackendExceptions("Requirements file not found")
215
+
130
216
  if os.name == "nt": # Windows
131
217
  pip_path = os.path.join(venv_path, "Scripts", "pip")
132
- else: # Linux/Mac
218
+ else: # Unix-based
133
219
  pip_path = os.path.join(venv_path, "bin", "pip")
134
220
 
221
+ # Upgrade pip
222
+ subprocess.run(
223
+ [pip_path, "install", "--upgrade", "pip"],
224
+ check=True,
225
+ capture_output=True,
226
+ text=True,
227
+ )
228
+
135
229
  with console.status("[bold green]Installing dependencies..."):
136
230
  subprocess.run(
137
231
  [pip_path, "install", "-r", "requirements.txt"],
138
232
  cwd=project_dir,
139
233
  check=True,
140
234
  )
141
- if os.name == "nt":
142
- activate_venv = f" {os.path.join(venv_path, 'Scripts', 'activate.bat')}"
143
- else:
144
- activate_venv = f" source {os.path.join(venv_path, 'bin', 'activate')}"
145
235
 
146
- print_info(
147
- "Dependencies installed successfully."
148
- + "\nTo activate the virtual environment, run:\n\n"
149
- + activate_venv,
150
- )
236
+ print_success("Dependencies installed successfully")
151
237
 
152
- except Exception as e:
153
- print_error(f"Error during dependency installation: {e}")
238
+ except subprocess.CalledProcessError as e:
239
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
240
+ if hasattr(e, "stderr"):
241
+ print_error(f"Error details: {e.stderr}")
154
242
  raise BackendExceptions("Failed to install dependencies")
243
+ except Exception as e:
244
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
245
+ raise BackendExceptions(f"Failed to install dependencies: {str(e)}")
155
246
 
156
247
 
157
- def read_template_stack(template_path: str) -> list[str]:
248
+ # ------------------------------------------------------------
249
+ # Add Route Functions
250
+ # ------------------------------------------------------------
251
+
252
+
253
+ def _ensure_project_structure(src_dir: str) -> Dict[str, str]:
158
254
  """
159
- Read the install_requires from setup.py-tpl in the template directory.
160
- Returns a list of required packages.
255
+ Ensure the project structure exists for adding a new route.
256
+ Creates necessary directories if they don't exist.
161
257
 
162
- :param template_path: Path to the template directory
163
- :return: List of required packages
258
+ :param src_dir: Source directory of the project
259
+ :return: Dictionary with paths to target directories
164
260
  """
165
- setup_path = os.path.join(template_path, "setup.py-tpl")
166
- if not os.path.exists(setup_path):
167
- return []
261
+ if not os.path.exists(src_dir):
262
+ raise BackendExceptions(f"Source directory not found at {src_dir}")
263
+
264
+ # Define target directories
265
+ target_dirs = {
266
+ "api": os.path.join(src_dir, "api"),
267
+ "api_routes": os.path.join(src_dir, "api", "routes"),
268
+ "crud": os.path.join(src_dir, "crud"),
269
+ "schemas": os.path.join(src_dir, "schemas"),
270
+ }
168
271
 
272
+ # Create directories and __init__.py files if needed
273
+ for dir_name, dir_path in target_dirs.items():
274
+ if not os.path.exists(dir_path):
275
+ os.makedirs(dir_path)
276
+
277
+ # Create __init__.py if it doesn't exist
278
+ init_file = os.path.join(dir_path, "__init__.py")
279
+ if not os.path.exists(init_file):
280
+ with open(init_file, "w") as f:
281
+ pass
282
+
283
+ return target_dirs
284
+
285
+
286
+ def _create_route_files(
287
+ modules_dir: str, target_dirs: Dict[str, str], route_name: str
288
+ ) -> None:
289
+ """
290
+ Create route files from templates.
291
+
292
+ :param modules_dir: Path to the modules directory
293
+ :param target_dirs: Dictionary with paths to target directories
294
+ :param route_name: Name of the route to create
295
+ """
296
+ replacements = {"<new_route>": route_name}
297
+ module_types = ["api/routes", "crud", "schemas"]
298
+
299
+ for module_type in module_types:
300
+ # Source template
301
+ source = os.path.join(modules_dir, module_type, "new_route.py-tpl")
302
+
303
+ # Target directory
304
+ if module_type == "api/routes":
305
+ target_dir = target_dirs["api_routes"]
306
+ else:
307
+ target_dir = target_dirs[module_type.split("/")[-1]]
308
+
309
+ # Target file
310
+ target = os.path.join(target_dir, f"{route_name}.py")
311
+
312
+ if os.path.exists(target):
313
+ print_warning(f"File {target} already exists, skipping...")
314
+ continue
315
+
316
+ if not copy_and_convert_template_file(source, target, replacements):
317
+ print_warning(f"Failed to copy template file {source} to {target}")
318
+
319
+
320
+ def _handle_api_router_file(
321
+ target_dirs: Dict[str, str], modules_dir: str, route_name: str
322
+ ) -> None:
323
+ """
324
+ Handle the API router file - create or update.
325
+
326
+ :param target_dirs: Dictionary with paths to target directories
327
+ :param modules_dir: Path to the modules directory
328
+ :param route_name: Name of the route to add
329
+ """
330
+ api_py_target = os.path.join(target_dirs["api"], "api.py")
331
+
332
+ if os.path.exists(api_py_target):
333
+ # Update existing api.py
334
+ with open(api_py_target, "r") as f:
335
+ api_content = f.read()
336
+
337
+ # Check if router is already included
338
+ router_import = f"from src.api.routes import {route_name}"
339
+ if router_import not in api_content:
340
+ with open(api_py_target, "a") as f:
341
+ f.write(f"\n{router_import}\n")
342
+ f.write(f"api_router.include_router({route_name}.router)\n")
343
+ print_info(f"Added route {route_name} to existing api.py")
344
+ else:
345
+ # Create new api.py
346
+ router_code = f"""
347
+ # --------------------------------------------------------------------------
348
+ # API router connector module
349
+ # --------------------------------------------------------------------------
350
+ from fastapi import APIRouter
351
+
352
+ from src.api.routes import {route_name}
353
+
354
+ api_router = APIRouter()
355
+ api_router.include_router({route_name}.router)
356
+
357
+ """
358
+ with open(api_py_target, "w") as tgt_file:
359
+ tgt_file.write(router_code)
360
+
361
+
362
+ def _process_init_files(
363
+ modules_dir: str, target_dirs: Dict[str, str], module_types: List[str]
364
+ ) -> None:
365
+ """
366
+ Process __init__.py files if they don't exist.
367
+
368
+ :param modules_dir: Path to the modules directory
369
+ :param target_dirs: Dictionary with paths to target directories
370
+ :param module_types: List of module types to process
371
+ """
372
+ for module_type in module_types:
373
+ module_base = module_type.split("/")[0]
374
+ init_source = os.path.join(modules_dir, module_base, "__init__.py-tpl")
375
+
376
+ if os.path.exists(init_source):
377
+ init_target_dir = target_dirs.get(module_base, None) or target_dirs.get(
378
+ f"{module_base}_routes"
379
+ )
380
+ if init_target_dir:
381
+ init_target = os.path.join(init_target_dir, "__init__.py")
382
+ if not os.path.exists(init_target):
383
+ copy_and_convert_template_file(init_source, init_target)
384
+
385
+
386
+ def _update_main_app(src_dir: str, route_name: str) -> None:
387
+ """
388
+ Update the main application file to include the API router.
389
+ Adds the router import and include statement at the end of the file.
390
+
391
+ :param src_dir: Source directory of the project
392
+ :param route_name: Name of the route to add
393
+ """
394
+ main_py_path = os.path.join(src_dir, "main.py")
395
+ if not os.path.exists(main_py_path):
396
+ print_warning("main.py not found. Please manually add the API router.")
397
+ return
398
+
399
+ with open(main_py_path, "r") as f:
400
+ main_content = f.read()
401
+
402
+ # Check if router is already imported or included
403
+ router_import = "from src.api.api import api_router"
404
+ router_include = "app.include_router(api_router"
405
+
406
+ if router_import in main_content and router_include in main_content:
407
+ # Router already fully configured, nothing to do
408
+ return
409
+
410
+ # Check if FastAPI app is defined
411
+ if "app = FastAPI" not in main_content:
412
+ print_warning(
413
+ "FastAPI app instance not found in main.py. Please manually add router."
414
+ )
415
+ return
416
+
417
+ # Add the router import if needed
418
+ if router_import not in main_content:
419
+ # Add import at the end of imports section or beginning of file
420
+ lines = main_content.split("\n")
421
+
422
+ # Find last import line
423
+ last_import_idx = -1
424
+ for i, line in enumerate(lines):
425
+ if line.startswith("import ") or line.startswith("from "):
426
+ last_import_idx = i
427
+
428
+ if last_import_idx >= 0:
429
+ # Insert after the last import
430
+ lines.insert(last_import_idx + 1, router_import)
431
+ else:
432
+ # Insert at the beginning
433
+ lines.insert(0, router_import)
434
+
435
+ main_content = "\n".join(lines)
436
+
437
+ # Add the router include
438
+ if router_include not in main_content:
439
+ if not main_content.endswith("\n"):
440
+ main_content += "\n"
441
+
442
+ main_content += f"\n# Include API router\napp.include_router(api_router)\n"
443
+
444
+ # Write the updated content back to the file
445
+ with open(main_py_path, "w") as f:
446
+ f.write(main_content)
447
+
448
+ print_info("Updated main.py to include the API router")
449
+
450
+
451
+ def add_new_route(project_dir: str, route_name: str) -> None:
452
+ """
453
+ Add a new API route to an existing FastAPI project.
454
+
455
+ :param project_dir: Path to the project directory
456
+ :param route_name: Name of the new route to add
457
+ :return: None
458
+ """
169
459
  try:
170
- with open(setup_path, "r") as f:
171
- content = f.read()
172
- # Find the install_requires section using proper string matching
173
- if "install_requires: list[str] = [" in content:
174
- start_idx = content.find("install_requires: list[str] = [") + len(
175
- "install_requires: list[str] = ["
176
- )
177
- end_idx = content.find("]", start_idx)
178
- if start_idx != -1 and end_idx != -1:
179
- deps_str = content[start_idx:end_idx]
180
- # Clean and parse the dependencies
181
- deps = [
182
- dep.strip().strip("'").strip('"')
183
- for dep in deps_str.split(",")
184
- if dep.strip() and not dep.isspace()
185
- ]
186
- return [dep for dep in deps if dep] # Remove empty strings
187
- except Exception as e:
188
- print_error(f"Error reading template dependencies: {e}")
189
- return []
460
+ # Setup paths
461
+ modules_dir = os.path.join(settings.FASTKIT_TEMPLATE_ROOT, "modules")
462
+ src_dir = os.path.join(project_dir, "src")
190
463
 
191
- return []
464
+ # Ensure project structure exists
465
+ target_dirs = _ensure_project_structure(src_dir)
466
+
467
+ # Create route files
468
+ _create_route_files(modules_dir, target_dirs, route_name)
469
+
470
+ # Handle API router file
471
+ _handle_api_router_file(target_dirs, modules_dir, route_name)
472
+
473
+ # Process init files
474
+ module_types = ["api/routes", "crud", "schemas"]
475
+ _process_init_files(modules_dir, target_dirs, module_types)
476
+
477
+ # Update main application
478
+ _update_main_app(src_dir, route_name)
479
+
480
+ except Exception as e:
481
+ handle_exception(e, f"Error adding new route: {str(e)}")
482
+ raise BackendExceptions(f"Failed to add new route: {str(e)}")
@@ -64,6 +64,42 @@ def copy_and_convert_template(
64
64
  shutil.copy2(src_file, dst_file)
65
65
 
66
66
 
67
+ def copy_and_convert_template_file(
68
+ source_file: str, target_file: str, replacements: dict = None # type: ignore
69
+ ) -> bool:
70
+ """
71
+ Copies a single template file to the target location, converting it from .*-tpl
72
+ to its proper extension and replacing any placeholders with provided values.
73
+
74
+ :param source_file: Path to the source template file (with -tpl extension)
75
+ :param target_file: Path to target destination file (without -tpl extension)
76
+ :param replacements: Dictionary of placeholder replacements {placeholder: value}
77
+ :return: True if successful, False otherwise
78
+ """
79
+ try:
80
+ if not os.path.exists(source_file):
81
+ return False
82
+
83
+ # Read a single source content (with .py-tpl extension)
84
+ with open(source_file, "r") as src_file:
85
+ content = src_file.read()
86
+
87
+ if replacements and isinstance(replacements, dict):
88
+ for placeholder, value in replacements.items():
89
+ content = content.replace(placeholder, value)
90
+
91
+ target_dir = os.path.dirname(target_file)
92
+ os.makedirs(target_dir, exist_ok=True)
93
+
94
+ with open(target_file, "w") as tgt_file:
95
+ tgt_file.write(content)
96
+
97
+ return True
98
+ except Exception as e:
99
+ print(f"Error copying template file: {e}")
100
+ return False
101
+
102
+
67
103
  def _convert_real_extension_to_tpl() -> None:
68
104
  # TODO : impl this for converting runnable FastAPI app code to template - debugging operation for contributors
69
105
  # this will be used at inspector module, not package user's runtime.