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,22 @@
1
+ # --------------------------------------------------------------------------
2
+ # Package Managers Module - FastAPI-fastkit
3
+ # Provides abstraction layer for different package managers (pip, uv, pdm, poetry)
4
+ #
5
+ # @author bnbong bbbong9@gmail.com
6
+ # --------------------------------------------------------------------------
7
+
8
+ from .base import BasePackageManager
9
+ from .factory import PackageManagerFactory
10
+ from .pdm_manager import PdmManager
11
+ from .pip_manager import PipManager
12
+ from .poetry_manager import PoetryManager
13
+ from .uv_manager import UvManager
14
+
15
+ __all__ = [
16
+ "BasePackageManager",
17
+ "PackageManagerFactory",
18
+ "PipManager",
19
+ "PdmManager",
20
+ "UvManager",
21
+ "PoetryManager",
22
+ ]
@@ -0,0 +1,150 @@
1
+ # --------------------------------------------------------------------------
2
+ # Base Package Manager - Abstract class for package manager implementations
3
+ #
4
+ # @author bnbong bbbong9@gmail.com
5
+ # --------------------------------------------------------------------------
6
+ import subprocess
7
+ from abc import ABC, abstractmethod
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class BasePackageManager(ABC):
13
+ """
14
+ Abstract base class for package managers.
15
+
16
+ All package manager implementations must inherit from this class
17
+ and implement the required abstract methods.
18
+ """
19
+
20
+ def __init__(self, project_dir: str):
21
+ """
22
+ Initialize package manager for a specific project.
23
+
24
+ :param project_dir: Path to the project directory
25
+ """
26
+ self.project_dir = Path(project_dir)
27
+ self.name = self.__class__.__name__.replace("Manager", "").lower()
28
+
29
+ @abstractmethod
30
+ def is_available(self) -> bool:
31
+ """
32
+ Check if the package manager is available on the system.
33
+
34
+ :return: True if package manager is installed and available
35
+ """
36
+ pass
37
+
38
+ @abstractmethod
39
+ def get_dependency_file_name(self) -> str:
40
+ """
41
+ Get the name of the dependency file for this package manager.
42
+
43
+ :return: Dependency file name (e.g., 'requirements.txt', 'pyproject.toml')
44
+ """
45
+ pass
46
+
47
+ @abstractmethod
48
+ def create_virtual_environment(self) -> str:
49
+ """
50
+ Create a virtual environment for the project.
51
+
52
+ :return: Path to the created virtual environment
53
+ :raises: Exception if virtual environment creation fails
54
+ """
55
+ pass
56
+
57
+ @abstractmethod
58
+ def install_dependencies(self, venv_path: str) -> None:
59
+ """
60
+ Install dependencies using the package manager.
61
+
62
+ :param venv_path: Path to the virtual environment
63
+ :raises: Exception if dependency installation fails
64
+ """
65
+ pass
66
+
67
+ @abstractmethod
68
+ def generate_dependency_file(
69
+ self,
70
+ dependencies: List[str],
71
+ project_name: str = "",
72
+ author: str = "",
73
+ author_email: str = "",
74
+ description: str = "",
75
+ ) -> None:
76
+ """
77
+ Generate a dependency file with the given dependencies and metadata.
78
+
79
+ :param dependencies: List of dependency specifications
80
+ :param project_name: Name of the project
81
+ :param author: Author name
82
+ :param author_email: Author email
83
+ :param description: Project description
84
+ """
85
+ pass
86
+
87
+ @abstractmethod
88
+ def add_dependency(self, dependency: str, dev: bool = False) -> None:
89
+ """
90
+ Add a new dependency to the project.
91
+
92
+ :param dependency: Dependency specification
93
+ :param dev: Whether this is a development dependency
94
+ """
95
+ pass
96
+
97
+ def get_executable_path(
98
+ self, executable_name: str, venv_path: Optional[str] = None
99
+ ) -> str:
100
+ """
101
+ Get the full path to an executable, considering virtual environment.
102
+
103
+ :param executable_name: Name of the executable
104
+ :param venv_path: Path to virtual environment (optional)
105
+ :return: Full path to the executable
106
+ """
107
+ import os
108
+
109
+ if venv_path:
110
+ if os.name == "nt": # Windows
111
+ return os.path.join(venv_path, "Scripts", f"{executable_name}.exe")
112
+ else: # Unix-based
113
+ return os.path.join(venv_path, "bin", executable_name)
114
+ else:
115
+ return executable_name
116
+
117
+ def run_command(
118
+ self, command: List[str], **kwargs: Any
119
+ ) -> subprocess.CompletedProcess[str]:
120
+ """
121
+ Run a command with proper error handling.
122
+
123
+ :param command: Command to run as list of strings
124
+ :param kwargs: Additional keyword arguments for subprocess.run
125
+ :return: CompletedProcess instance
126
+ :raises: subprocess.CalledProcessError on failure
127
+ """
128
+ default_kwargs: Dict[str, Any] = {
129
+ "check": True,
130
+ "capture_output": True,
131
+ "text": True,
132
+ "cwd": str(self.project_dir),
133
+ }
134
+ default_kwargs.update(kwargs)
135
+
136
+ return subprocess.run(command, **default_kwargs)
137
+
138
+ def get_dependency_file_path(self) -> Path:
139
+ """
140
+ Get the full path to the dependency file.
141
+
142
+ :return: Path to the dependency file
143
+ """
144
+ return self.project_dir / self.get_dependency_file_name()
145
+
146
+ def __str__(self) -> str:
147
+ return f"{self.__class__.__name__}({self.project_dir})"
148
+
149
+ def __repr__(self) -> str:
150
+ return self.__str__()
@@ -0,0 +1,151 @@
1
+ # --------------------------------------------------------------------------
2
+ # Package Manager Factory - Creates appropriate package manager instances
3
+ #
4
+ # @author bnbong bbbong9@gmail.com
5
+ # --------------------------------------------------------------------------
6
+ from typing import Dict, List, Optional, Type
7
+
8
+ from fastapi_fastkit.core.exceptions import BackendExceptions
9
+ from fastapi_fastkit.utils.logging import debug_log, get_logger
10
+
11
+ from .base import BasePackageManager
12
+ from .pdm_manager import PdmManager
13
+ from .pip_manager import PipManager
14
+ from .poetry_manager import PoetryManager
15
+ from .uv_manager import UvManager
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class PackageManagerFactory:
21
+ """Factory for creating package manager instances."""
22
+
23
+ # Registry of available package managers
24
+ _managers: Dict[str, Type[BasePackageManager]] = {
25
+ "pip": PipManager,
26
+ "pdm": PdmManager,
27
+ "uv": UvManager,
28
+ "poetry": PoetryManager,
29
+ }
30
+
31
+ @classmethod
32
+ def create_manager(
33
+ self, manager_type: str, project_dir: str, auto_detect: bool = False
34
+ ) -> BasePackageManager:
35
+ """
36
+ Create a package manager instance.
37
+
38
+ :param manager_type: Type of package manager ('pip', 'uv', 'pdm', 'poetry')
39
+ :param project_dir: Path to the project directory
40
+ :param auto_detect: If True, auto-detect available package manager when requested type is not available
41
+ :return: Package manager instance
42
+ :raises: BackendExceptions if manager type is not supported or not available
43
+ """
44
+ manager_type = manager_type.lower()
45
+
46
+ # Check if requested manager type is supported
47
+ if manager_type not in self._managers:
48
+ available_managers = list(self._managers.keys())
49
+ error_msg = f"Unsupported package manager: {manager_type}. Available: {available_managers}"
50
+ debug_log(error_msg, "error")
51
+ raise BackendExceptions(error_msg)
52
+
53
+ # Create the manager instance
54
+ manager_class = self._managers[manager_type]
55
+ manager = manager_class(project_dir)
56
+
57
+ # Check if the manager is available on the system
58
+ if not manager.is_available():
59
+ if auto_detect:
60
+ debug_log(
61
+ f"{manager_type} is not available, trying to auto-detect alternative",
62
+ "warning",
63
+ )
64
+ return self._auto_detect_manager(project_dir, exclude=[manager_type])
65
+ else:
66
+ error_msg = (
67
+ f"Package manager '{manager_type}' is not available on the system"
68
+ )
69
+ debug_log(error_msg, "error")
70
+ raise BackendExceptions(error_msg)
71
+
72
+ debug_log(f"Created {manager_type} package manager for {project_dir}", "info")
73
+ return manager
74
+
75
+ @classmethod
76
+ def _auto_detect_manager(
77
+ self, project_dir: str, exclude: Optional[List[str]] = None
78
+ ) -> BasePackageManager:
79
+ """
80
+ Auto-detect the best available package manager.
81
+
82
+ :param project_dir: Path to the project directory
83
+ :param exclude: List of manager types to exclude from detection
84
+ :return: Package manager instance
85
+ :raises: BackendExceptions if no package manager is available
86
+ """
87
+ exclude = exclude or []
88
+
89
+ # Priority order for auto-detection
90
+ detection_order = ["uv", "pdm", "poetry", "pip"]
91
+
92
+ for manager_type in detection_order:
93
+ if manager_type in exclude:
94
+ continue
95
+
96
+ if manager_type not in self._managers:
97
+ continue
98
+
99
+ manager_class = self._managers[manager_type]
100
+ manager = manager_class(project_dir)
101
+
102
+ if manager.is_available():
103
+ debug_log(f"Auto-detected package manager: {manager_type}", "info")
104
+ return manager
105
+
106
+ error_msg = "No package manager is available on the system"
107
+ debug_log(error_msg, "error")
108
+ raise BackendExceptions(error_msg)
109
+
110
+ @classmethod
111
+ def get_available_managers(self) -> list[str]:
112
+ """
113
+ Get list of available package managers on the current system.
114
+
115
+ :return: List of available package manager names
116
+ """
117
+ available = []
118
+
119
+ for manager_type, manager_class in self._managers.items():
120
+ # Create a temporary instance to check availability
121
+ # Using current directory as a dummy project path
122
+ temp_manager = manager_class(".")
123
+ if temp_manager.is_available():
124
+ available.append(manager_type)
125
+
126
+ return available
127
+
128
+ @classmethod
129
+ def register_manager(
130
+ self, name: str, manager_class: Type[BasePackageManager]
131
+ ) -> None:
132
+ """
133
+ Register a new package manager type.
134
+
135
+ :param name: Name of the package manager
136
+ :param manager_class: Package manager class
137
+ """
138
+ if not issubclass(manager_class, BasePackageManager):
139
+ raise ValueError(f"Manager class must inherit from BasePackageManager")
140
+
141
+ self._managers[name.lower()] = manager_class
142
+ debug_log(f"Registered package manager: {name}", "info")
143
+
144
+ @classmethod
145
+ def get_supported_managers(self) -> list[str]:
146
+ """
147
+ Get list of all supported package manager types.
148
+
149
+ :return: List of supported package manager names
150
+ """
151
+ return list(self._managers.keys())
@@ -0,0 +1,275 @@
1
+ # --------------------------------------------------------------------------
2
+ # PDM 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 PdmManager(BasePackageManager):
22
+ """PDM package manager implementation."""
23
+
24
+ def is_available(self) -> bool:
25
+ """Check if PDM is available on the system."""
26
+ try:
27
+ subprocess.run(
28
+ ["pdm", "--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 PDM."""
39
+ return "pyproject.toml"
40
+
41
+ def create_virtual_environment(self) -> str:
42
+ """
43
+ Create a virtual environment using PDM.
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 with PDM..."):
52
+ # PDM can create virtual environment in specific location
53
+ subprocess.run(
54
+ ["pdm", "venv", "create", "--name", ".venv", sys.executable],
55
+ cwd=str(self.project_dir),
56
+ check=True,
57
+ capture_output=True,
58
+ text=True,
59
+ )
60
+
61
+ debug_log(f"Virtual environment created at {venv_path}", "info")
62
+ print_success("Virtual environment created successfully with PDM")
63
+ return venv_path
64
+
65
+ except subprocess.CalledProcessError as e:
66
+ debug_log(f"Error creating virtual environment: {e.stderr}", "error")
67
+ handle_exception(e, f"Error creating virtual environment: {str(e)}")
68
+ raise BackendExceptions("Failed to create venv with PDM")
69
+ except OSError as e:
70
+ debug_log(f"System error creating virtual environment: {e}", "error")
71
+ handle_exception(e, f"Error creating virtual environment: {str(e)}")
72
+ raise BackendExceptions(f"Failed to create venv with PDM: {str(e)}")
73
+
74
+ def install_dependencies(self, venv_path: str) -> None:
75
+ """
76
+ Install dependencies using PDM.
77
+
78
+ :param venv_path: Path to the virtual environment
79
+ :raises: BackendExceptions if dependency installation fails
80
+ """
81
+ try:
82
+ if not os.path.exists(venv_path):
83
+ debug_log(
84
+ "Virtual environment does not exist. Creating it first.", "warning"
85
+ )
86
+ print_error("Virtual environment does not exist. Creating it first.")
87
+ venv_path = self.create_virtual_environment()
88
+ if not venv_path:
89
+ raise BackendExceptions("Failed to create venv")
90
+
91
+ pyproject_path = self.get_dependency_file_path()
92
+ if not pyproject_path.exists():
93
+ debug_log(f"pyproject.toml file not found at {pyproject_path}", "error")
94
+ print_error(f"pyproject.toml file not found at {pyproject_path}")
95
+ raise BackendExceptions("pyproject.toml file not found")
96
+
97
+ # Install dependencies using PDM
98
+ with console.status("[bold green]Installing dependencies with PDM..."):
99
+ subprocess.run(
100
+ ["pdm", "install"],
101
+ cwd=str(self.project_dir),
102
+ check=True,
103
+ capture_output=True,
104
+ text=True,
105
+ )
106
+
107
+ debug_log("Dependencies installed successfully with PDM", "info")
108
+ print_success("Dependencies installed successfully with PDM")
109
+
110
+ except subprocess.CalledProcessError as e:
111
+ debug_log(f"Error during dependency installation: {e.stderr}", "error")
112
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
113
+ if hasattr(e, "stderr"):
114
+ print_error(f"Error details: {e.stderr}")
115
+ raise BackendExceptions("Failed to install dependencies with PDM")
116
+ except OSError as e:
117
+ debug_log(f"System error during dependency installation: {e}", "error")
118
+ handle_exception(e, f"Error during dependency installation: {str(e)}")
119
+ raise BackendExceptions(
120
+ f"Failed to install dependencies with PDM: {str(e)}"
121
+ )
122
+
123
+ def generate_dependency_file(
124
+ self,
125
+ dependencies: List[str],
126
+ project_name: str = "",
127
+ author: str = "",
128
+ author_email: str = "",
129
+ description: str = "",
130
+ ) -> None:
131
+ """
132
+ Generate a pyproject.toml file with the given dependencies and metadata.
133
+
134
+ :param dependencies: List of dependency specifications
135
+ :param project_name: Name of the project
136
+ :param author: Author name
137
+ :param author_email: Author email
138
+ :param description: Project description
139
+ """
140
+ pyproject_path = self.get_dependency_file_path()
141
+
142
+ try:
143
+ # Create dependencies list as TOML format
144
+ deps_toml = "[\n"
145
+ for dep in dependencies:
146
+ deps_toml += f' "{dep}",\n'
147
+ deps_toml += "]"
148
+
149
+ # Create basic pyproject.toml content as string
150
+ pyproject_content = f"""[project]
151
+ name = "{project_name or 'fastapi-project'}"
152
+ version = "0.1.0"
153
+ description = "{description or 'A FastAPI project'}"
154
+ authors = [
155
+ {{name = "{author or 'Author'}", email = "{author_email or 'author@example.com'}"}},
156
+ ]
157
+ dependencies = {deps_toml}
158
+ requires-python = ">=3.8"
159
+ readme = "README.md"
160
+ license = {{text = "MIT"}}
161
+
162
+ [build-system]
163
+ requires = ["pdm-backend"]
164
+ build-backend = "pdm.backend"
165
+
166
+ [tool.hatch.build.targets.wheel]
167
+ packages = ["src"]
168
+
169
+ [tool.pdm]
170
+ """
171
+
172
+ with open(pyproject_path, "w", encoding="utf-8") as f:
173
+ f.write(pyproject_content)
174
+
175
+ debug_log(
176
+ f"Generated {pyproject_path} with {len(dependencies)} dependencies",
177
+ "info",
178
+ )
179
+
180
+ except (OSError, UnicodeEncodeError) as e:
181
+ debug_log(f"Error generating pyproject.toml: {e}", "error")
182
+ raise BackendExceptions(f"Failed to generate pyproject.toml: {str(e)}")
183
+
184
+ def add_dependency(self, dependency: str, dev: bool = False) -> None:
185
+ """
186
+ Add a new dependency using PDM.
187
+
188
+ :param dependency: Dependency specification
189
+ :param dev: Whether this is a development dependency
190
+ """
191
+ try:
192
+ # Use PDM's add command to add dependency
193
+ cmd = ["pdm", "add"]
194
+ if dev:
195
+ cmd.append("--dev")
196
+ cmd.append(dependency)
197
+
198
+ subprocess.run(
199
+ cmd,
200
+ cwd=str(self.project_dir),
201
+ check=True,
202
+ capture_output=True,
203
+ text=True,
204
+ )
205
+
206
+ debug_log(
207
+ f"Added {'dev ' if dev else ''}dependency '{dependency}' with PDM",
208
+ "info",
209
+ )
210
+
211
+ except subprocess.CalledProcessError as e:
212
+ debug_log(f"Error adding dependency with PDM: {e}", "error")
213
+ raise BackendExceptions(f"Failed to add dependency with PDM: {str(e)}")
214
+ except OSError as e:
215
+ debug_log(f"System error adding dependency with PDM: {e}", "error")
216
+ raise BackendExceptions(f"Failed to add dependency with PDM: {str(e)}")
217
+
218
+ def initialize_project(
219
+ self, project_name: str, author: str, author_email: str, description: str
220
+ ) -> None:
221
+ """
222
+ Initialize a new PDM project with metadata.
223
+
224
+ :param project_name: Name of the project
225
+ :param author: Author name
226
+ :param author_email: Author email
227
+ :param description: Project description
228
+ """
229
+ try:
230
+ # Use PDM init command for interactive setup
231
+ subprocess.run(
232
+ ["pdm", "init", "--non-interactive"],
233
+ cwd=str(self.project_dir),
234
+ check=True,
235
+ capture_output=True,
236
+ text=True,
237
+ )
238
+
239
+ # Create or update pyproject.toml with provided metadata
240
+ pyproject_path = self.get_dependency_file_path()
241
+
242
+ # Create basic pyproject.toml with project metadata
243
+ pyproject_content = f"""[project]
244
+ name = "{project_name}"
245
+ version = "0.1.0"
246
+ description = "{description}"
247
+ authors = [
248
+ {{name = "{author}", email = "{author_email}"}},
249
+ ]
250
+ dependencies = []
251
+ requires-python = ">=3.8"
252
+ readme = "README.md"
253
+ license = {{text = "MIT"}}
254
+
255
+ [build-system]
256
+ requires = ["pdm-backend"]
257
+ build-backend = "pdm.backend"
258
+
259
+ [tool.hatch.build.targets.wheel]
260
+ packages = ["src"]
261
+
262
+ [tool.pdm]
263
+ """
264
+
265
+ with open(pyproject_path, "w", encoding="utf-8") as f:
266
+ f.write(pyproject_content)
267
+
268
+ debug_log(f"Initialized PDM project: {project_name}", "info")
269
+
270
+ except subprocess.CalledProcessError as e:
271
+ debug_log(f"Error initializing PDM project: {e}", "error")
272
+ raise BackendExceptions(f"Failed to initialize PDM project: {str(e)}")
273
+ except (OSError, UnicodeEncodeError) as e:
274
+ debug_log(f"Error updating project metadata: {e}", "error")
275
+ raise BackendExceptions(f"Failed to update project metadata: {str(e)}")