FastAPI-fastkit 1.0.2__py3-none-any.whl → 1.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.
Files changed (174) hide show
  1. fastapi_fastkit/__init__.py +1 -10
  2. fastapi_fastkit/backend/inspector.py +933 -83
  3. fastapi_fastkit/backend/main.py +640 -482
  4. fastapi_fastkit/backend/package_managers/__init__.py +22 -0
  5. fastapi_fastkit/backend/package_managers/base.py +150 -0
  6. fastapi_fastkit/backend/package_managers/factory.py +151 -0
  7. fastapi_fastkit/backend/package_managers/pdm_manager.py +275 -0
  8. fastapi_fastkit/backend/package_managers/pip_manager.py +205 -0
  9. fastapi_fastkit/backend/package_managers/poetry_manager.py +378 -0
  10. fastapi_fastkit/backend/package_managers/uv_manager.py +327 -0
  11. fastapi_fastkit/backend/transducer.py +210 -106
  12. fastapi_fastkit/cli.py +702 -664
  13. fastapi_fastkit/core/exceptions.py +14 -3
  14. fastapi_fastkit/core/settings.py +181 -155
  15. fastapi_fastkit/fastapi_project_template/README.md +90 -90
  16. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.env-tpl +1 -1
  17. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/.gitignore-tpl +30 -30
  18. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +107 -107
  19. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +36 -36
  20. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/format.sh-tpl +5 -5
  21. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/lint.sh-tpl +5 -5
  22. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/run-server.sh-tpl +8 -8
  23. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/scripts/test.sh-tpl +6 -6
  24. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.cfg-tpl +14 -14
  25. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +25 -25
  26. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/api.py-tpl +9 -9
  27. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/routes/items.py-tpl +57 -57
  28. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/config.py-tpl +64 -64
  29. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/items.py-tpl +21 -21
  30. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/main.py-tpl +26 -26
  31. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/mocks/mock_items.json-tpl +8 -8
  32. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/items.py-tpl +22 -22
  33. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/conftest.py-tpl +42 -42
  34. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/test_items.py-tpl +48 -48
  35. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.env-tpl +1 -1
  36. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/.gitignore-tpl +191 -191
  37. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/README.md-tpl +156 -156
  38. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +36 -36
  39. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/format.sh-tpl +5 -5
  40. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/lint.sh-tpl +5 -5
  41. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/run-server.sh-tpl +8 -8
  42. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/scripts/test.sh-tpl +6 -6
  43. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.cfg-tpl +12 -12
  44. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.py-tpl +27 -27
  45. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/api.py-tpl +9 -9
  46. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/items.py-tpl +172 -172
  47. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/config.py-tpl +64 -64
  48. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/items.py-tpl +21 -21
  49. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/exceptions.py-tpl +103 -103
  50. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/pagination.py-tpl +72 -72
  51. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/main.py-tpl +39 -39
  52. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/mocks/mock_items.json-tpl +8 -8
  53. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/base.py-tpl +49 -49
  54. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/items.py-tpl +22 -22
  55. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/utils/documents.py-tpl +20 -20
  56. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/conftest.py-tpl +42 -42
  57. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/test_items.py-tpl +61 -61
  58. fastapi_fastkit/fastapi_project_template/fastapi-default/README.md-tpl +101 -101
  59. fastapi_fastkit/fastapi_project_template/fastapi-default/scripts/run-server.sh-tpl +8 -8
  60. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +22 -22
  61. fastapi_fastkit/fastapi_project_template/fastapi-default/src/mocks/mock_items.json-tpl +8 -8
  62. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +30 -30
  63. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +44 -44
  64. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.env-tpl +1 -1
  65. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/.gitignore-tpl +30 -30
  66. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/Dockerfile-tpl +23 -23
  67. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +119 -119
  68. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +36 -36
  69. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/format.sh-tpl +5 -5
  70. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/lint.sh-tpl +5 -5
  71. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/run-server.sh-tpl +8 -8
  72. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/scripts/test.sh-tpl +6 -6
  73. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.cfg-tpl +13 -13
  74. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.py-tpl +22 -22
  75. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/api.py-tpl +6 -6
  76. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/items.py-tpl +54 -54
  77. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/config.py-tpl +64 -64
  78. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/items.py-tpl +15 -15
  79. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/main.py-tpl +26 -26
  80. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/mocks/mock_items.json-tpl +8 -8
  81. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/items.py-tpl +19 -19
  82. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/conftest.py-tpl +30 -30
  83. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/test_items.py-tpl +44 -44
  84. fastapi_fastkit/fastapi_project_template/fastapi-empty/.env-tpl +1 -1
  85. fastapi_fastkit/fastapi_project_template/fastapi-empty/.gitignore-tpl +30 -0
  86. fastapi_fastkit/fastapi_project_template/fastapi-empty/README.md-tpl +85 -0
  87. fastapi_fastkit/fastapi_project_template/fastapi-empty/requirements.txt-tpl +9 -0
  88. fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.cfg-tpl +13 -0
  89. fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.py-tpl +22 -22
  90. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/config.py-tpl +75 -75
  91. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/main.py-tpl +48 -36
  92. fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/__init__.py-tpl +0 -0
  93. fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/conftest.py-tpl +16 -0
  94. fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/test_main.py-tpl +37 -0
  95. fastapi_fastkit/fastapi_project_template/fastapi-mcp/.env-tpl +29 -0
  96. fastapi_fastkit/fastapi_project_template/fastapi-mcp/README.md-tpl +249 -0
  97. fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +40 -0
  98. fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/format.sh-tpl +12 -0
  99. fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/lint.sh-tpl +18 -0
  100. fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/run-server.sh-tpl +9 -0
  101. fastapi_fastkit/fastapi_project_template/fastapi-mcp/scripts/test.sh-tpl +9 -0
  102. fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.cfg-tpl +90 -0
  103. fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.py-tpl +51 -0
  104. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/__init__.py-tpl +4 -0
  105. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/__init__.py-tpl +3 -0
  106. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/api.py-tpl +14 -0
  107. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/__init__.py-tpl +3 -0
  108. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/auth.py-tpl +134 -0
  109. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/items.py-tpl +189 -0
  110. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/auth/__init__.py-tpl +3 -0
  111. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/auth/dependencies.py-tpl +88 -0
  112. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/core/__init__.py-tpl +3 -0
  113. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/core/config.py-tpl +60 -0
  114. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/main.py-tpl +66 -0
  115. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/mcp/__init__.py-tpl +3 -0
  116. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/mcp/router.py-tpl +100 -0
  117. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/schemas/__init__.py-tpl +3 -0
  118. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/schemas/items.py-tpl +67 -0
  119. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/__init__.py-tpl +3 -0
  120. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/conftest.py-tpl +49 -0
  121. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_auth.py-tpl +101 -0
  122. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_items.py-tpl +96 -0
  123. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_mcp.py-tpl +81 -0
  124. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.env-tpl +9 -9
  125. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/.gitignore-tpl +30 -30
  126. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/Dockerfile-tpl +23 -23
  127. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/README.md-tpl +144 -144
  128. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/alembic.ini-tpl +119 -119
  129. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/docker-compose.yml-tpl +44 -44
  130. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +41 -41
  131. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/format.sh-tpl +5 -5
  132. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/lint.sh-tpl +5 -5
  133. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/pre-start.sh-tpl +16 -16
  134. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/run-server.sh-tpl +8 -8
  135. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/scripts/test.sh-tpl +10 -10
  136. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.cfg-tpl +13 -13
  137. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.py-tpl +25 -25
  138. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/README-tpl +1 -1
  139. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/env.py-tpl +85 -85
  140. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/script.py.mako-tpl +26 -26
  141. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/versions/bedcdc35b64a_first_alembic.py-tpl +37 -37
  142. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/api.py-tpl +11 -11
  143. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/deps.py-tpl +19 -19
  144. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/items.py-tpl +109 -109
  145. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/config.py-tpl +88 -88
  146. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/db.py-tpl +53 -53
  147. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/items.py-tpl +102 -102
  148. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/main.py-tpl +45 -45
  149. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/items.py-tpl +54 -54
  150. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/backend_pre_start.py-tpl +39 -39
  151. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/init_data.py-tpl +23 -23
  152. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/utils/tests_pre_start.py-tpl +39 -39
  153. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/template-config.yml-tpl +24 -0
  154. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/conftest.py-tpl +63 -63
  155. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/test_items.py-tpl +44 -44
  156. fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl +64 -64
  157. fastapi_fastkit/fastapi_project_template/modules/crud/new_route.py-tpl +29 -29
  158. fastapi_fastkit/fastapi_project_template/modules/schemas/new_route.py-tpl +23 -23
  159. fastapi_fastkit/utils/logging.py +64 -28
  160. fastapi_fastkit/utils/main.py +180 -140
  161. fastapi_fastkit-1.1.0.dist-info/METADATA +182 -0
  162. {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/RECORD +165 -129
  163. {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/WHEEL +1 -1
  164. {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/licenses/LICENSE +21 -21
  165. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/.DS_Store +0 -0
  166. fastapi_fastkit/logs/fastkit_debug_20250702_161423.log +0 -21
  167. fastapi_fastkit/logs/fastkit_debug_20250702_162706.log +0 -20
  168. fastapi_fastkit/logs/fastkit_debug_20250702_163010.log +0 -21
  169. fastapi_fastkit/logs/fastkit_debug_20250702_163526.log +0 -21
  170. fastapi_fastkit/logs/fastkit_debug_20250702_164053.log +0 -21
  171. fastapi_fastkit/logs/fastkit_debug_20250702_165220.log +0 -21
  172. fastapi_fastkit/logs/fastkit_debug_20250702_165647.log +0 -21
  173. fastapi_fastkit-1.0.2.dist-info/METADATA +0 -320
  174. {fastapi_fastkit-1.0.2.dist-info → fastapi_fastkit-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,205 @@
1
+ # --------------------------------------------------------------------------
2
+ # Pip Package Manager Implementation
3
+ #
4
+ # @author bnbong bbbong9@gmail.com
5
+ # --------------------------------------------------------------------------
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from typing import List
10
+
11
+ from fastapi_fastkit import console
12
+ from fastapi_fastkit.core.exceptions import BackendExceptions
13
+ from fastapi_fastkit.utils.logging import debug_log, get_logger
14
+ from fastapi_fastkit.utils.main import handle_exception, print_error, print_success
15
+
16
+ from .base import BasePackageManager
17
+
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ class PipManager(BasePackageManager):
22
+ """Pip package manager implementation."""
23
+
24
+ def is_available(self) -> bool:
25
+ """Check if pip is available on the system."""
26
+ try:
27
+ subprocess.run(
28
+ [sys.executable, "-m", "pip", "--version"],
29
+ check=True,
30
+ capture_output=True,
31
+ text=True,
32
+ )
33
+ return True
34
+ except (subprocess.CalledProcessError, FileNotFoundError):
35
+ return False
36
+
37
+ def get_dependency_file_name(self) -> str:
38
+ """Get the dependency file name for pip."""
39
+ return "requirements.txt"
40
+
41
+ def create_virtual_environment(self) -> str:
42
+ """
43
+ Create a Python virtual environment using venv module.
44
+
45
+ :return: Path to the virtual environment
46
+ :raises: BackendExceptions if virtual environment creation fails
47
+ """
48
+ venv_path = str(self.project_dir / ".venv")
49
+
50
+ try:
51
+ with console.status("[bold green]Creating virtual environment..."):
52
+ subprocess.run(
53
+ [sys.executable, "-m", "venv", venv_path],
54
+ check=True,
55
+ capture_output=True,
56
+ text=True,
57
+ )
58
+
59
+ debug_log(f"Virtual environment created at {venv_path}", "info")
60
+ print_success("Virtual environment created successfully")
61
+ return venv_path
62
+
63
+ except subprocess.CalledProcessError as e:
64
+ debug_log(f"Error creating virtual environment: {e.stderr}", "error")
65
+ handle_exception(e, f"Error creating virtual environment: {str(e)}")
66
+ raise BackendExceptions("Failed to create venv")
67
+ except OSError as e:
68
+ debug_log(f"System error creating virtual environment: {e}", "error")
69
+ handle_exception(e, f"Error creating virtual environment: {str(e)}")
70
+ raise BackendExceptions(f"Failed to create venv: {str(e)}")
71
+
72
+ def install_dependencies(self, venv_path: str) -> None:
73
+ """
74
+ Install dependencies using pip in the virtual environment.
75
+
76
+ :param venv_path: Path to the virtual environment
77
+ :raises: BackendExceptions if dependency installation fails
78
+ """
79
+ try:
80
+ if not os.path.exists(venv_path):
81
+ debug_log(
82
+ "Virtual environment does not exist. Creating it first.", "warning"
83
+ )
84
+ print_error("Virtual environment does not exist. Creating it first.")
85
+ venv_path = self.create_virtual_environment()
86
+ if not venv_path:
87
+ raise BackendExceptions("Failed to create venv")
88
+
89
+ requirements_path = self.get_dependency_file_path()
90
+ if not requirements_path.exists():
91
+ debug_log(
92
+ f"Requirements file not found at {requirements_path}", "error"
93
+ )
94
+ print_error(f"Requirements file not found at {requirements_path}")
95
+ raise BackendExceptions("Requirements file not found")
96
+
97
+ # Get pip path
98
+ pip_path = self.get_executable_path("pip", venv_path)
99
+
100
+ # Upgrade pip first
101
+ subprocess.run(
102
+ [pip_path, "install", "--upgrade", "pip"],
103
+ check=True,
104
+ capture_output=True,
105
+ text=True,
106
+ )
107
+
108
+ # Install dependencies
109
+ with console.status("[bold green]Installing dependencies..."):
110
+ subprocess.run(
111
+ [pip_path, "install", "-r", str(requirements_path.name)],
112
+ cwd=str(self.project_dir),
113
+ check=True,
114
+ capture_output=True,
115
+ text=True,
116
+ )
117
+
118
+ debug_log("Dependencies installed successfully", "info")
119
+ print_success("Dependencies installed successfully")
120
+
121
+ except subprocess.CalledProcessError as e:
122
+ debug_log(f"Error during dependency installation: {e.stderr}", "error")
123
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
124
+ if hasattr(e, "stderr"):
125
+ print_error(f"Error details: {e.stderr}")
126
+ raise BackendExceptions("Failed to install dependencies")
127
+ except OSError as e:
128
+ debug_log(f"System error during dependency installation: {e}", "error")
129
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
130
+ raise BackendExceptions(f"Failed to install dependencies: {str(e)}")
131
+
132
+ def generate_dependency_file(
133
+ self,
134
+ dependencies: List[str],
135
+ project_name: str = "",
136
+ author: str = "",
137
+ author_email: str = "",
138
+ description: str = "",
139
+ ) -> None:
140
+ """
141
+ Generate a requirements.txt file with the given dependencies.
142
+
143
+ :param dependencies: List of dependency specifications
144
+ :param project_name: Name of the project (not used for pip)
145
+ :param author: Author name (not used for pip)
146
+ :param author_email: Author email (not used for pip)
147
+ :param description: Project description (not used for pip)
148
+ """
149
+ requirements_path = self.get_dependency_file_path()
150
+
151
+ try:
152
+ with open(requirements_path, "w", encoding="utf-8") as f:
153
+ for dep in dependencies:
154
+ f.write(f"{dep}\n")
155
+
156
+ debug_log(
157
+ f"Generated {requirements_path} with {len(dependencies)} dependencies",
158
+ "info",
159
+ )
160
+
161
+ except (OSError, UnicodeEncodeError) as e:
162
+ debug_log(f"Error generating requirements.txt: {e}", "error")
163
+ raise BackendExceptions(f"Failed to generate requirements.txt: {str(e)}")
164
+
165
+ def add_dependency(self, dependency: str, dev: bool = False) -> None:
166
+ """
167
+ Add a new dependency to requirements.txt.
168
+
169
+ Note: pip doesn't have built-in support for dev dependencies,
170
+ so we'll add them to the main requirements.txt file.
171
+
172
+ :param dependency: Dependency specification
173
+ :param dev: Whether this is a development dependency (ignored for pip)
174
+ """
175
+ requirements_path = self.get_dependency_file_path()
176
+
177
+ try:
178
+ # Read existing dependencies
179
+ existing_deps = []
180
+ if requirements_path.exists():
181
+ with open(requirements_path, "r", encoding="utf-8") as f:
182
+ existing_deps = [
183
+ line.strip() for line in f.readlines() if line.strip()
184
+ ]
185
+
186
+ # Add new dependency if not already present
187
+ if dependency not in existing_deps:
188
+ existing_deps.append(dependency)
189
+
190
+ with open(requirements_path, "w", encoding="utf-8") as f:
191
+ for dep in existing_deps:
192
+ f.write(f"{dep}\n")
193
+
194
+ debug_log(
195
+ f"Added dependency '{dependency}' to requirements.txt", "info"
196
+ )
197
+ else:
198
+ debug_log(
199
+ f"Dependency '{dependency}' already exists in requirements.txt",
200
+ "info",
201
+ )
202
+
203
+ except (OSError, UnicodeEncodeError, UnicodeDecodeError) as e:
204
+ debug_log(f"Error adding dependency: {e}", "error")
205
+ raise BackendExceptions(f"Failed to add dependency: {str(e)}")
@@ -0,0 +1,378 @@
1
+ # --------------------------------------------------------------------------
2
+ # Poetry Package Manager Implementation
3
+ #
4
+ # @author bnbong bbbong9@gmail.com
5
+ # --------------------------------------------------------------------------
6
+ import subprocess
7
+ from typing import List
8
+
9
+ from fastapi_fastkit import console
10
+ from fastapi_fastkit.core.exceptions import BackendExceptions
11
+ from fastapi_fastkit.utils.logging import debug_log, get_logger
12
+ from fastapi_fastkit.utils.main import handle_exception, print_error, print_success
13
+
14
+ from .base import BasePackageManager
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class PoetryManager(BasePackageManager):
20
+ """Poetry package manager implementation."""
21
+
22
+ def is_available(self) -> bool:
23
+ """Check if Poetry is available on the system."""
24
+ try:
25
+ subprocess.run(
26
+ ["poetry", "--version"],
27
+ check=True,
28
+ capture_output=True,
29
+ text=True,
30
+ )
31
+ return True
32
+ except (subprocess.CalledProcessError, FileNotFoundError):
33
+ return False
34
+
35
+ def get_dependency_file_name(self) -> str:
36
+ """Get the dependency file name for Poetry."""
37
+ return "pyproject.toml"
38
+
39
+ def create_virtual_environment(self) -> str:
40
+ """
41
+ Create a virtual environment using Poetry.
42
+
43
+ :return: Path to the virtual environment
44
+ :raises: BackendExceptions if virtual environment creation fails
45
+ """
46
+ try:
47
+ with console.status(
48
+ "[bold green]Creating virtual environment with Poetry..."
49
+ ):
50
+ # Poetry automatically creates virtual environment when installing
51
+ # First ensure we have a basic pyproject.toml
52
+ pyproject_path = self.get_dependency_file_path()
53
+ if not pyproject_path.exists():
54
+ # Create minimal pyproject.toml for Poetry
55
+ self._create_minimal_pyproject()
56
+
57
+ # Get the virtual environment path from Poetry
58
+ result = subprocess.run(
59
+ ["poetry", "env", "info", "--path"],
60
+ cwd=str(self.project_dir),
61
+ capture_output=True,
62
+ text=True,
63
+ check=False, # Don't fail if venv doesn't exist yet
64
+ )
65
+
66
+ if result.returncode != 0:
67
+ # Create virtual environment with Poetry
68
+ subprocess.run(
69
+ ["poetry", "install", "--no-deps"],
70
+ cwd=str(self.project_dir),
71
+ check=True,
72
+ capture_output=True,
73
+ text=True,
74
+ )
75
+
76
+ # Get the path again
77
+ result = subprocess.run(
78
+ ["poetry", "env", "info", "--path"],
79
+ cwd=str(self.project_dir),
80
+ capture_output=True,
81
+ text=True,
82
+ check=True,
83
+ )
84
+
85
+ venv_path = result.stdout.strip()
86
+
87
+ debug_log(f"Virtual environment created at {venv_path}", "info")
88
+ print_success("Virtual environment created successfully with Poetry")
89
+ return venv_path
90
+
91
+ except subprocess.CalledProcessError as e:
92
+ debug_log(f"Error creating virtual environment: {e.stderr}", "error")
93
+ handle_exception(e, f"Error creating virtual environment: {str(e)}")
94
+ raise BackendExceptions("Failed to create venv with Poetry")
95
+ except OSError as e:
96
+ debug_log(f"System error creating virtual environment: {e}", "error")
97
+ handle_exception(e, f"Error creating virtual environment: {str(e)}")
98
+ raise BackendExceptions(f"Failed to create venv with Poetry: {str(e)}")
99
+
100
+ def _create_minimal_pyproject(self) -> None:
101
+ """Create a minimal pyproject.toml for Poetry."""
102
+ pyproject_content = """[tool.poetry]
103
+ name = "temp-project"
104
+ version = "0.1.0"
105
+ description = ""
106
+ authors = ["Author <author@example.com>"]
107
+
108
+ [tool.poetry.dependencies]
109
+ python = "^3.8"
110
+
111
+ [tool.poetry.group.dev.dependencies]
112
+
113
+ [build-system]
114
+ requires = ["poetry-core"]
115
+ build-backend = "poetry.core.masonry.api"
116
+ """
117
+ pyproject_path = self.get_dependency_file_path()
118
+ with open(pyproject_path, "w", encoding="utf-8") as f:
119
+ f.write(pyproject_content)
120
+
121
+ def install_dependencies(self, venv_path: str) -> None:
122
+ """
123
+ Install dependencies using Poetry.
124
+
125
+ :param venv_path: Path to the virtual environment
126
+ :raises: BackendExceptions if dependency installation fails
127
+ """
128
+ try:
129
+ pyproject_path = self.get_dependency_file_path()
130
+ if not pyproject_path.exists():
131
+ debug_log(f"pyproject.toml file not found at {pyproject_path}", "error")
132
+ print_error(f"pyproject.toml file not found at {pyproject_path}")
133
+ raise BackendExceptions("pyproject.toml file not found")
134
+
135
+ # Install dependencies using Poetry
136
+ with console.status("[bold green]Installing dependencies with Poetry..."):
137
+ subprocess.run(
138
+ ["poetry", "install"],
139
+ cwd=str(self.project_dir),
140
+ check=True,
141
+ capture_output=True,
142
+ text=True,
143
+ )
144
+
145
+ debug_log("Dependencies installed successfully with Poetry", "info")
146
+ print_success("Dependencies installed successfully with Poetry")
147
+
148
+ except subprocess.CalledProcessError as e:
149
+ debug_log(f"Error during dependency installation: {e.stderr}", "error")
150
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
151
+ if hasattr(e, "stderr"):
152
+ print_error(f"Error details: {e.stderr}")
153
+ raise BackendExceptions("Failed to install dependencies with Poetry")
154
+ except OSError as e:
155
+ debug_log(f"System error during dependency installation: {e}", "error")
156
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
157
+ raise BackendExceptions(
158
+ f"Failed to install dependencies with Poetry: {str(e)}"
159
+ )
160
+
161
+ def generate_dependency_file(
162
+ self,
163
+ dependencies: List[str],
164
+ project_name: str = "",
165
+ author: str = "",
166
+ author_email: str = "",
167
+ description: str = "",
168
+ ) -> None:
169
+ """
170
+ Generate a pyproject.toml file with the given dependencies and metadata.
171
+
172
+ :param dependencies: List of dependency specifications
173
+ :param project_name: Name of the project
174
+ :param author: Author name
175
+ :param author_email: Author email
176
+ :param description: Project description
177
+ """
178
+ pyproject_path = self.get_dependency_file_path()
179
+
180
+ try:
181
+ # Create dependencies section for Poetry
182
+ deps_section = ""
183
+ for dep in dependencies:
184
+ # Convert pip-style to poetry-style
185
+ if "==" in dep:
186
+ name, version = dep.split("==", 1)
187
+ deps_section += f'{name} = "{version}"\n'
188
+ else:
189
+ deps_section += f'{dep} = "*"\n'
190
+
191
+ # Create basic pyproject.toml content for Poetry
192
+ pyproject_content = f"""[tool.poetry]
193
+ name = "{project_name or 'fastapi-project'}"
194
+ version = "0.1.0"
195
+ description = "{description or 'A FastAPI project'}"
196
+ authors = ["{author or 'Author'} <{author_email or 'author@example.com'}>"]
197
+ readme = "README.md"
198
+ license = "MIT"
199
+ packages = [{{include = "src"}}]
200
+
201
+ [tool.poetry.dependencies]
202
+ python = "^3.8"
203
+ {deps_section}
204
+
205
+ [tool.poetry.group.dev.dependencies]
206
+
207
+ [build-system]
208
+ requires = ["poetry-core"]
209
+ build-backend = "poetry.core.masonry.api"
210
+ """
211
+
212
+ with open(pyproject_path, "w", encoding="utf-8") as f:
213
+ f.write(pyproject_content)
214
+
215
+ debug_log(
216
+ f"Generated {pyproject_path} with {len(dependencies)} dependencies",
217
+ "info",
218
+ )
219
+
220
+ except (OSError, UnicodeEncodeError) as e:
221
+ debug_log(f"Error generating pyproject.toml: {e}", "error")
222
+ raise BackendExceptions(f"Failed to generate pyproject.toml: {str(e)}")
223
+
224
+ def add_dependency(self, dependency: str, dev: bool = False) -> None:
225
+ """
226
+ Add a new dependency using Poetry.
227
+
228
+ :param dependency: Dependency specification
229
+ :param dev: Whether this is a development dependency
230
+ """
231
+ try:
232
+ # Use Poetry's add command to add dependency
233
+ cmd = ["poetry", "add"]
234
+ if dev:
235
+ cmd.append("--group=dev")
236
+ cmd.append(dependency)
237
+
238
+ subprocess.run(
239
+ cmd,
240
+ cwd=str(self.project_dir),
241
+ check=True,
242
+ capture_output=True,
243
+ text=True,
244
+ )
245
+
246
+ debug_log(
247
+ f"Added {'dev ' if dev else ''}dependency '{dependency}' with Poetry",
248
+ "info",
249
+ )
250
+
251
+ except subprocess.CalledProcessError as e:
252
+ debug_log(f"Error adding dependency with Poetry: {e}", "error")
253
+ raise BackendExceptions(f"Failed to add dependency with Poetry: {str(e)}")
254
+ except OSError as e:
255
+ debug_log(f"System error adding dependency with Poetry: {e}", "error")
256
+ raise BackendExceptions(f"Failed to add dependency with Poetry: {str(e)}")
257
+
258
+ def initialize_project(
259
+ self, project_name: str, author: str, author_email: str, description: str
260
+ ) -> None:
261
+ """
262
+ Initialize a new Poetry project with metadata.
263
+
264
+ :param project_name: Name of the project
265
+ :param author: Author name
266
+ :param author_email: Author email
267
+ :param description: Project description
268
+ """
269
+ try:
270
+ # Use Poetry init command with non-interactive mode
271
+ subprocess.run(
272
+ [
273
+ "poetry",
274
+ "init",
275
+ "--name",
276
+ project_name,
277
+ "--description",
278
+ description,
279
+ "--author",
280
+ f"{author} <{author_email}>",
281
+ "--license",
282
+ "MIT",
283
+ "--python",
284
+ "^3.8",
285
+ "--no-interaction",
286
+ ],
287
+ cwd=str(self.project_dir),
288
+ check=True,
289
+ capture_output=True,
290
+ text=True,
291
+ )
292
+
293
+ debug_log(f"Initialized Poetry project: {project_name}", "info")
294
+
295
+ except subprocess.CalledProcessError as e:
296
+ debug_log(f"Error initializing Poetry project: {e}", "error")
297
+ raise BackendExceptions(f"Failed to initialize Poetry project: {str(e)}")
298
+ except OSError as e:
299
+ debug_log(f"System error initializing Poetry project: {e}", "error")
300
+ raise BackendExceptions(f"Failed to initialize Poetry project: {str(e)}")
301
+
302
+ def lock_dependencies(self) -> None:
303
+ """
304
+ Generate Poetry lock file.
305
+
306
+ :raises: BackendExceptions if lock generation fails
307
+ """
308
+ try:
309
+ with console.status("[bold green]Generating Poetry lock file..."):
310
+ subprocess.run(
311
+ ["poetry", "lock"],
312
+ cwd=str(self.project_dir),
313
+ check=True,
314
+ capture_output=True,
315
+ text=True,
316
+ )
317
+
318
+ debug_log("Poetry lock file generated successfully", "info")
319
+ print_success("Poetry lock file generated successfully")
320
+
321
+ except subprocess.CalledProcessError as e:
322
+ debug_log(f"Error generating Poetry lock file: {e.stderr}", "error")
323
+ handle_exception(e, f"Error generating Poetry lock file: {str(e)}")
324
+ raise BackendExceptions("Failed to generate Poetry lock file")
325
+ except OSError as e:
326
+ debug_log(f"System error generating Poetry lock file: {e}", "error")
327
+ handle_exception(e, f"Error generating Poetry lock file: {str(e)}")
328
+ raise BackendExceptions(f"Failed to generate Poetry lock file: {str(e)}")
329
+
330
+ def run_script(self, script_command: str) -> None:
331
+ """
332
+ Run a script using Poetry.
333
+
334
+ :param script_command: Command to run
335
+ :raises: BackendExceptions if script execution fails
336
+ """
337
+ try:
338
+ subprocess.run(
339
+ ["poetry", "run"] + script_command.split(),
340
+ cwd=str(self.project_dir),
341
+ check=True,
342
+ capture_output=True,
343
+ text=True,
344
+ )
345
+
346
+ debug_log(f"Poetry script executed successfully: {script_command}", "info")
347
+
348
+ except subprocess.CalledProcessError as e:
349
+ debug_log(f"Error running Poetry script: {e.stderr}", "error")
350
+ raise BackendExceptions(f"Failed to run Poetry script: {str(e)}")
351
+ except OSError as e:
352
+ debug_log(f"System error running Poetry script: {e}", "error")
353
+ raise BackendExceptions(f"Failed to run Poetry script: {str(e)}")
354
+
355
+ def show_dependencies(self) -> str:
356
+ """
357
+ Show project dependencies using Poetry.
358
+
359
+ :return: Dependencies information
360
+ :raises: BackendExceptions if showing dependencies fails
361
+ """
362
+ try:
363
+ result = subprocess.run(
364
+ ["poetry", "show"],
365
+ cwd=str(self.project_dir),
366
+ check=True,
367
+ capture_output=True,
368
+ text=True,
369
+ )
370
+
371
+ return result.stdout
372
+
373
+ except subprocess.CalledProcessError as e:
374
+ debug_log(f"Error showing Poetry dependencies: {e.stderr}", "error")
375
+ raise BackendExceptions(f"Failed to show Poetry dependencies: {str(e)}")
376
+ except OSError as e:
377
+ debug_log(f"System error showing Poetry dependencies: {e}", "error")
378
+ raise BackendExceptions(f"Failed to show Poetry dependencies: {str(e)}")