FastAPI-fastkit 1.2.1__py3-none-any.whl → 1.4.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 (356) hide show
  1. fastapi_fastkit/__init__.py +1 -1
  2. fastapi_fastkit/backend/inspection/__init__.py +26 -0
  3. fastapi_fastkit/backend/inspection/checks.py +349 -0
  4. fastapi_fastkit/backend/inspection/consistency.py +125 -0
  5. fastapi_fastkit/backend/inspection/context.py +67 -0
  6. fastapi_fastkit/backend/inspection/core.py +347 -0
  7. fastapi_fastkit/backend/inspection/docker.py +185 -0
  8. fastapi_fastkit/backend/inspection/freshness.py +145 -0
  9. fastapi_fastkit/backend/inspection/fsutils.py +139 -0
  10. fastapi_fastkit/backend/inspection/lint.py +89 -0
  11. fastapi_fastkit/backend/inspection/report.py +58 -0
  12. fastapi_fastkit/backend/inspection/smoke.py +327 -0
  13. fastapi_fastkit/backend/inspection/strategies.py +336 -0
  14. fastapi_fastkit/backend/inspector.py +18 -1279
  15. fastapi_fastkit/backend/interactive/__init__.py +8 -0
  16. fastapi_fastkit/backend/interactive/config_builder.py +66 -65
  17. fastapi_fastkit/backend/interactive/prompts.py +205 -5
  18. fastapi_fastkit/backend/interactive/selectors.py +28 -2
  19. fastapi_fastkit/backend/main.py +956 -95
  20. fastapi_fastkit/backend/package_managers/base.py +172 -0
  21. fastapi_fastkit/backend/package_managers/factory.py +6 -5
  22. fastapi_fastkit/backend/package_managers/pdm_manager.py +32 -85
  23. fastapi_fastkit/backend/package_managers/pip_manager.py +31 -85
  24. fastapi_fastkit/backend/package_managers/poetry_manager.py +73 -113
  25. fastapi_fastkit/backend/package_managers/uv_manager.py +46 -115
  26. fastapi_fastkit/backend/project_builder/__init__.py +10 -0
  27. fastapi_fastkit/backend/project_builder/config_generator.py +813 -450
  28. fastapi_fastkit/backend/project_builder/config_schema.py +384 -0
  29. fastapi_fastkit/backend/project_builder/dependency_collector.py +105 -66
  30. fastapi_fastkit/backend/project_builder/preset_layout.py +429 -0
  31. fastapi_fastkit/backend/scaffolder.py +578 -0
  32. fastapi_fastkit/backend/transducer.py +54 -22
  33. fastapi_fastkit/cli.py +452 -284
  34. fastapi_fastkit/core/settings.py +248 -43
  35. fastapi_fastkit/fastapi_project_template/README.md +88 -23
  36. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/README.md-tpl +3 -0
  37. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/pyproject.toml-tpl +25 -23
  38. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/requirements.txt-tpl +39 -36
  39. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/api.py-tpl +3 -0
  40. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/api/routes/items.py-tpl +1 -2
  41. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/core/config.py-tpl +5 -6
  42. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/crud/items.py-tpl +1 -2
  43. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/main.py-tpl +9 -1
  44. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/src/schemas/items.py-tpl +1 -2
  45. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/tests/conftest.py-tpl +0 -1
  46. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/.env-tpl +21 -0
  47. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/.gitignore-tpl +33 -0
  48. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/Dockerfile-tpl +18 -0
  49. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/README.md-tpl +210 -0
  50. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/alembic.ini-tpl +119 -0
  51. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/pyproject.toml-tpl +86 -0
  52. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/requirements.txt-tpl +23 -0
  53. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/format.sh-tpl +5 -0
  54. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/lint.sh-tpl +6 -0
  55. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/run-server.sh-tpl +8 -0
  56. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/scripts/test.sh-tpl +6 -0
  57. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/__init__.py-tpl +0 -0
  58. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/__init__.py-tpl +0 -0
  59. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/README-tpl +1 -0
  60. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/env.py-tpl +51 -0
  61. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/script.py.mako-tpl +26 -0
  62. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/alembic/versions/0001_initial_auth_schema.py-tpl +62 -0
  63. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/__init__.py-tpl +0 -0
  64. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/deps.py-tpl +91 -0
  65. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/health.py-tpl +11 -0
  66. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/api/router.py-tpl +17 -0
  67. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/core/__init__.py-tpl +0 -0
  68. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/core/config.py-tpl +64 -0
  69. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/core/security.py-tpl +100 -0
  70. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/db/__init__.py-tpl +0 -0
  71. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/db/session.py-tpl +36 -0
  72. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/__init__.py-tpl +0 -0
  73. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/__init__.py-tpl +0 -0
  74. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/models.py-tpl +25 -0
  75. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/repository.py-tpl +41 -0
  76. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/router.py-tpl +82 -0
  77. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/schemas.py-tpl +14 -0
  78. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/auth/service.py-tpl +65 -0
  79. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/__init__.py-tpl +0 -0
  80. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/models.py-tpl +35 -0
  81. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/repository.py-tpl +33 -0
  82. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/router.py-tpl +71 -0
  83. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/schemas.py-tpl +29 -0
  84. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/domains/users/service.py-tpl +55 -0
  85. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/src/app/main.py-tpl +69 -0
  86. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/template-config.yml-tpl +16 -0
  87. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/__init__.py-tpl +0 -0
  88. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/conftest.py-tpl +73 -0
  89. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/test_auth.py-tpl +201 -0
  90. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/test_health.py-tpl +15 -0
  91. fastapi_fastkit/fastapi_project_template/fastapi-auth-jwt/tests/test_users.py-tpl +77 -0
  92. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/pyproject.toml-tpl +25 -23
  93. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/requirements.txt-tpl +39 -36
  94. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/api.py-tpl +3 -0
  95. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/api/routes/items.py-tpl +3 -3
  96. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/core/config.py-tpl +5 -6
  97. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/crud/items.py-tpl +1 -2
  98. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/helper/pagination.py-tpl +8 -8
  99. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/main.py-tpl +9 -0
  100. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/base.py-tpl +1 -1
  101. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/src/schemas/items.py-tpl +1 -2
  102. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/tests/conftest.py-tpl +0 -1
  103. fastapi_fastkit/fastapi_project_template/fastapi-default/pyproject.toml-tpl +22 -20
  104. fastapi_fastkit/fastapi_project_template/fastapi-default/requirements.txt-tpl +37 -34
  105. fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/api.py-tpl +3 -0
  106. fastapi_fastkit/fastapi_project_template/fastapi-default/src/api/routes/items.py-tpl +1 -2
  107. fastapi_fastkit/fastapi_project_template/fastapi-default/src/core/config.py-tpl +5 -6
  108. fastapi_fastkit/fastapi_project_template/fastapi-default/src/crud/items.py-tpl +1 -2
  109. fastapi_fastkit/fastapi_project_template/fastapi-default/src/main.py-tpl +9 -1
  110. fastapi_fastkit/fastapi_project_template/fastapi-default/src/schemas/items.py-tpl +1 -2
  111. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/conftest.py-tpl +2 -3
  112. fastapi_fastkit/fastapi_project_template/fastapi-default/tests/test_items.py-tpl +0 -2
  113. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/Dockerfile-tpl +1 -1
  114. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/README.md-tpl +3 -0
  115. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/pyproject.toml-tpl +25 -23
  116. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/requirements.txt-tpl +41 -36
  117. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/api.py-tpl +3 -0
  118. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/api/routes/items.py-tpl +1 -2
  119. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/core/config.py-tpl +5 -6
  120. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/crud/items.py-tpl +1 -2
  121. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/main.py-tpl +9 -1
  122. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/src/schemas/items.py-tpl +1 -2
  123. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/conftest.py-tpl +2 -3
  124. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/tests/test_items.py-tpl +0 -2
  125. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.env-tpl +2 -0
  126. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/.gitignore-tpl +31 -0
  127. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/README.md-tpl +128 -0
  128. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/pyproject.toml-tpl +69 -0
  129. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/requirements.txt-tpl +37 -0
  130. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/format.sh-tpl +5 -0
  131. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/lint.sh-tpl +6 -0
  132. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/run-server.sh-tpl +8 -0
  133. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/scripts/test.sh-tpl +6 -0
  134. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/__init__.py-tpl +0 -0
  135. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/__init__.py-tpl +0 -0
  136. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/__init__.py-tpl +0 -0
  137. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/health.py-tpl +11 -0
  138. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/api/router.py-tpl +15 -0
  139. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/__init__.py-tpl +0 -0
  140. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/core/config.py-tpl +49 -0
  141. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/__init__.py-tpl +0 -0
  142. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/db/memory.py-tpl +49 -0
  143. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/__init__.py-tpl +0 -0
  144. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/__init__.py-tpl +9 -0
  145. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/models.py-tpl +17 -0
  146. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/repository.py-tpl +47 -0
  147. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/router.py-tpl +58 -0
  148. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/schemas.py-tpl +23 -0
  149. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/domains/items/service.py-tpl +51 -0
  150. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/src/app/main.py-tpl +28 -0
  151. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/__init__.py-tpl +0 -0
  152. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/conftest.py-tpl +23 -0
  153. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_health.py-tpl +11 -0
  154. fastapi_fastkit/fastapi_project_template/fastapi-domain-starter/tests/test_items.py-tpl +77 -0
  155. fastapi_fastkit/fastapi_project_template/fastapi-empty/README.md-tpl +2 -2
  156. fastapi_fastkit/fastapi_project_template/fastapi-empty/pyproject.toml-tpl +18 -16
  157. fastapi_fastkit/fastapi_project_template/fastapi-empty/requirements.txt-tpl +36 -10
  158. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/core/config.py-tpl +5 -6
  159. fastapi_fastkit/fastapi_project_template/fastapi-empty/src/main.py-tpl +3 -1
  160. fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/conftest.py-tpl +0 -1
  161. fastapi_fastkit/fastapi_project_template/fastapi-empty/tests/test_main.py-tpl +0 -1
  162. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/.env-tpl +15 -0
  163. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/.gitignore-tpl +31 -0
  164. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/Dockerfile-tpl +18 -0
  165. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/README.md-tpl +193 -0
  166. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/pyproject.toml-tpl +76 -0
  167. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/requirements.txt-tpl +15 -0
  168. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/chat.sh-tpl +10 -0
  169. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/format.sh-tpl +5 -0
  170. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/lint.sh-tpl +6 -0
  171. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/run-server.sh-tpl +8 -0
  172. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/scripts/test.sh-tpl +6 -0
  173. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/__init__.py-tpl +0 -0
  174. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/__init__.py-tpl +0 -0
  175. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/__init__.py-tpl +0 -0
  176. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/chat.py-tpl +62 -0
  177. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/deps.py-tpl +23 -0
  178. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/health.py-tpl +11 -0
  179. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/api/router.py-tpl +13 -0
  180. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/core/__init__.py-tpl +0 -0
  181. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/core/config.py-tpl +67 -0
  182. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/__init__.py-tpl +0 -0
  183. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/agent.py-tpl +136 -0
  184. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/client.py-tpl +30 -0
  185. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/llm/tools.py-tpl +131 -0
  186. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/main.py-tpl +53 -0
  187. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/memory/__init__.py-tpl +0 -0
  188. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/memory/base.py-tpl +27 -0
  189. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/memory/in_memory.py-tpl +32 -0
  190. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/schemas/__init__.py-tpl +0 -0
  191. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/src/app/schemas/chat.py-tpl +46 -0
  192. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/template-config.yml-tpl +16 -0
  193. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/__init__.py-tpl +0 -0
  194. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/conftest.py-tpl +158 -0
  195. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_chat.py-tpl +160 -0
  196. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_health.py-tpl +12 -0
  197. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_memory.py-tpl +38 -0
  198. fastapi_fastkit/fastapi_project_template/fastapi-llm-agent/tests/test_tools.py-tpl +37 -0
  199. fastapi_fastkit/fastapi_project_template/fastapi-mcp/README.md-tpl +1 -1
  200. fastapi_fastkit/fastapi_project_template/fastapi-mcp/pyproject.toml-tpl +29 -26
  201. fastapi_fastkit/fastapi_project_template/fastapi-mcp/requirements.txt-tpl +68 -39
  202. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/api.py-tpl +3 -0
  203. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/auth.py-tpl +4 -5
  204. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/api/routes/items.py-tpl +3 -4
  205. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/auth/dependencies.py-tpl +4 -5
  206. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/core/config.py-tpl +4 -5
  207. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/main.py-tpl +4 -1
  208. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/{mcp → mcp_server}/router.py-tpl +3 -5
  209. fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/schemas/items.py-tpl +6 -7
  210. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/conftest.py-tpl +1 -1
  211. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_auth.py-tpl +0 -1
  212. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_items.py-tpl +0 -1
  213. fastapi_fastkit/fastapi_project_template/fastapi-mcp/tests/test_mcp.py-tpl +0 -1
  214. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/Dockerfile-tpl +1 -1
  215. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/pyproject.toml-tpl +26 -24
  216. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/requirements.txt-tpl +44 -41
  217. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/env.py-tpl +1 -3
  218. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/alembic/versions/bedcdc35b64a_first_alembic.py-tpl +5 -6
  219. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/api.py-tpl +2 -0
  220. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/api/routes/items.py-tpl +2 -4
  221. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/config.py-tpl +1 -2
  222. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/core/db.py-tpl +1 -1
  223. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/crud/items.py-tpl +4 -6
  224. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/main.py-tpl +9 -0
  225. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/src/schemas/items.py-tpl +4 -5
  226. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/conftest.py-tpl +1 -5
  227. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/tests/test_items.py-tpl +0 -2
  228. fastapi_fastkit/fastapi_project_template/fastapi-single-module/.env-tpl +1 -0
  229. fastapi_fastkit/fastapi_project_template/fastapi-single-module/.gitignore-tpl +30 -0
  230. fastapi_fastkit/fastapi_project_template/fastapi-single-module/pyproject.toml-tpl +7 -5
  231. fastapi_fastkit/fastapi_project_template/fastapi-single-module/requirements.txt-tpl +26 -6
  232. fastapi_fastkit/fastapi_project_template/fastapi-single-module/src/main.py-tpl +3 -0
  233. fastapi_fastkit/fastapi_project_template/fastapi-single-module/tests/__init__.py-tpl +0 -0
  234. fastapi_fastkit/fastapi_project_template/fastapi-single-module/tests/conftest.py-tpl +0 -1
  235. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/.env-tpl +7 -0
  236. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/.gitignore-tpl +35 -0
  237. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/Dockerfile-tpl +18 -0
  238. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/README.md-tpl +166 -0
  239. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/alembic.ini-tpl +44 -0
  240. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/pyproject.toml-tpl +82 -0
  241. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/requirements.txt-tpl +21 -0
  242. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/format.sh-tpl +5 -0
  243. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/lint.sh-tpl +6 -0
  244. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/migrate.sh-tpl +7 -0
  245. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/run-server.sh-tpl +8 -0
  246. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/scripts/test.sh-tpl +6 -0
  247. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/__init__.py-tpl +0 -0
  248. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/__init__.py-tpl +0 -0
  249. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/README-tpl +9 -0
  250. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/env.py-tpl +72 -0
  251. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/script.py.mako-tpl +27 -0
  252. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/alembic/versions/0001_create_items_table.py-tpl +38 -0
  253. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/api/__init__.py-tpl +0 -0
  254. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/api/health.py-tpl +11 -0
  255. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/api/router.py-tpl +15 -0
  256. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/core/__init__.py-tpl +0 -0
  257. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/core/config.py-tpl +62 -0
  258. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/crud/__init__.py-tpl +0 -0
  259. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/crud/base.py-tpl +62 -0
  260. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/crud/pagination.py-tpl +21 -0
  261. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/db/__init__.py-tpl +0 -0
  262. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/db/base.py-tpl +12 -0
  263. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/db/session.py-tpl +50 -0
  264. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/__init__.py-tpl +0 -0
  265. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/__init__.py-tpl +0 -0
  266. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/crud.py-tpl +26 -0
  267. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/models.py-tpl +70 -0
  268. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/domains/items/router.py-tpl +69 -0
  269. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/src/app/main.py-tpl +43 -0
  270. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/template-config.yml-tpl +16 -0
  271. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/__init__.py-tpl +0 -0
  272. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/conftest.py-tpl +60 -0
  273. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/test_crud_base.py-tpl +43 -0
  274. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/test_health.py-tpl +12 -0
  275. fastapi_fastkit/fastapi_project_template/fastapi-sqlmodel/tests/test_items.py-tpl +110 -0
  276. fastapi_fastkit/fastapi_project_template/modules/api/routes/new_route.py-tpl +2 -2
  277. fastapi_fastkit/fragments/README.md +90 -0
  278. fastapi_fastkit/fragments/auth/fastapi_users.py.j2 +8 -0
  279. fastapi_fastkit/fragments/auth/jwt.py.j2 +44 -0
  280. fastapi_fastkit/fragments/auth/oauth2.py.j2 +105 -0
  281. fastapi_fastkit/fragments/auth/session.py.j2 +67 -0
  282. fastapi_fastkit/fragments/db/mongodb.py.j2 +19 -0
  283. fastapi_fastkit/fragments/db/redis.py.j2 +26 -0
  284. fastapi_fastkit/fragments/db/sqlalchemy.py.j2 +45 -0
  285. fastapi_fastkit/fragments/docker/docker_compose.j2 +84 -0
  286. fastapi_fastkit/fragments/docker/dockerfile.j2 +19 -0
  287. fastapi_fastkit/fragments/features/cache.py.j2 +26 -0
  288. fastapi_fastkit/fragments/features/pagination.py.j2 +31 -0
  289. fastapi_fastkit/fragments/features/tasks_celery.py.j2 +50 -0
  290. fastapi_fastkit/fragments/features/tasks_dramatiq.py.j2 +59 -0
  291. fastapi_fastkit/fragments/features/websocket.py.j2 +66 -0
  292. fastapi_fastkit/fragments/header.j2 +4 -0
  293. fastapi_fastkit/fragments/logging/structured.py.j2 +88 -0
  294. fastapi_fastkit/fragments/main/imports/async_tasks.j2 +1 -0
  295. fastapi_fastkit/fragments/main/imports/caching.j2 +5 -0
  296. fastapi_fastkit/fragments/main/imports/fastapi_users.j2 +1 -0
  297. fastapi_fastkit/fragments/main/imports/jwt.j2 +1 -0
  298. fastapi_fastkit/fragments/main/imports/loguru.j2 +1 -0
  299. fastapi_fastkit/fragments/main/imports/mongodb.j2 +1 -0
  300. fastapi_fastkit/fragments/main/imports/oauth2.j2 +4 -0
  301. fastapi_fastkit/fragments/main/imports/opentelemetry.j2 +6 -0
  302. fastapi_fastkit/fragments/main/imports/pagination.j2 +3 -0
  303. fastapi_fastkit/fragments/main/imports/prometheus.j2 +1 -0
  304. fastapi_fastkit/fragments/main/imports/rate_limiting.j2 +3 -0
  305. fastapi_fastkit/fragments/main/imports/session_auth.j2 +4 -0
  306. fastapi_fastkit/fragments/main/imports/sqlalchemy.j2 +2 -0
  307. fastapi_fastkit/fragments/main/imports/structured_logging.j2 +1 -0
  308. fastapi_fastkit/fragments/main/imports/websocket.j2 +1 -0
  309. fastapi_fastkit/fragments/main/lifespan/caching.j2 +7 -0
  310. fastapi_fastkit/fragments/main/lifespan/mongodb.j2 +2 -0
  311. fastapi_fastkit/fragments/main/lifespan/prometheus.j2 +2 -0
  312. fastapi_fastkit/fragments/main/lifespan/sqlalchemy.j2 +2 -0
  313. fastapi_fastkit/fragments/main/setup/async_tasks.j2 +1 -0
  314. fastapi_fastkit/fragments/main/setup/caching.j2 +1 -0
  315. fastapi_fastkit/fragments/main/setup/cors.j2 +8 -0
  316. fastapi_fastkit/fragments/main/setup/oauth2.j2 +3 -0
  317. fastapi_fastkit/fragments/main/setup/opentelemetry.j2 +19 -0
  318. fastapi_fastkit/fragments/main/setup/pagination.j2 +2 -0
  319. fastapi_fastkit/fragments/main/setup/rate_limiting.j2 +3 -0
  320. fastapi_fastkit/fragments/main/setup/session_auth.j2 +6 -0
  321. fastapi_fastkit/fragments/main/setup/structured_logging.j2 +2 -0
  322. fastapi_fastkit/fragments/main/setup/websocket.j2 +1 -0
  323. fastapi_fastkit/fragments/main.py.j2 +57 -0
  324. fastapi_fastkit/fragments/migrations/alembic_ini.j2 +42 -0
  325. fastapi_fastkit/fragments/migrations/env.py.j2 +76 -0
  326. fastapi_fastkit/fragments/migrations/initial_revision.py.j2 +27 -0
  327. fastapi_fastkit/fragments/migrations/migrate_sh.j2 +34 -0
  328. fastapi_fastkit/fragments/migrations/script.py.mako.j2 +26 -0
  329. fastapi_fastkit/fragments/tasks/celery_worker.py.j2 +49 -0
  330. fastapi_fastkit/fragments/tasks/dramatiq_worker.py.j2 +41 -0
  331. fastapi_fastkit/fragments/test/factories.py.j2 +68 -0
  332. fastapi_fastkit/fragments/test/pytest_ini.j2 +16 -0
  333. fastapi_fastkit/fragments/test/test_factories.py.j2 +27 -0
  334. fastapi_fastkit/fragments/test/test_main.py.j2 +46 -0
  335. fastapi_fastkit/fragments/tooling/devcontainer.j2 +26 -0
  336. fastapi_fastkit/fragments/tooling/github_actions.j2 +37 -0
  337. fastapi_fastkit/fragments/tooling/makefile.j2 +40 -0
  338. fastapi_fastkit/fragments/tooling/pre_commit.j2 +36 -0
  339. fastapi_fastkit/fragments/tooling/ruff_toml.j2 +16 -0
  340. fastapi_fastkit/utils/config_file.py +263 -0
  341. fastapi_fastkit/utils/main.py +111 -6
  342. {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/METADATA +33 -6
  343. fastapi_fastkit-1.4.0.dist-info/RECORD +484 -0
  344. {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/WHEEL +1 -1
  345. fastapi_fastkit/fastapi_project_template/fastapi-async-crud/setup.py-tpl +0 -25
  346. fastapi_fastkit/fastapi_project_template/fastapi-custom-response/setup.py-tpl +0 -27
  347. fastapi_fastkit/fastapi_project_template/fastapi-default/setup.py-tpl +0 -22
  348. fastapi_fastkit/fastapi_project_template/fastapi-dockerized/setup.py-tpl +0 -22
  349. fastapi_fastkit/fastapi_project_template/fastapi-empty/setup.py-tpl +0 -22
  350. fastapi_fastkit/fastapi_project_template/fastapi-mcp/setup.py-tpl +0 -51
  351. fastapi_fastkit/fastapi_project_template/fastapi-psql-orm/setup.py-tpl +0 -25
  352. fastapi_fastkit/fastapi_project_template/fastapi-single-module/setup.py-tpl +0 -17
  353. fastapi_fastkit-1.2.1.dist-info/RECORD +0 -256
  354. /fastapi_fastkit/fastapi_project_template/fastapi-mcp/src/{mcp → mcp_server}/__init__.py-tpl +0 -0
  355. {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/entry_points.txt +0 -0
  356. {fastapi_fastkit-1.2.1.dist-info → fastapi_fastkit-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,336 @@
1
+ # --------------------------------------------------------------------------
2
+ # Test strategies for template inspection.
3
+ #
4
+ # All strategies share one base: prepare an environment, pick between the
5
+ # template's own ``scripts/test.sh`` and a direct pytest invocation, then turn
6
+ # a non-zero return code into an inspection error. The concrete strategies
7
+ # only supply what actually differs - the environment and the command.
8
+ #
9
+ # @author bnbong
10
+ # --------------------------------------------------------------------------
11
+ import os
12
+ import subprocess
13
+ from abc import ABC, abstractmethod
14
+ from typing import Any, Dict, List
15
+
16
+ from fastapi_fastkit.backend.main import create_venv, install_dependencies_with_manager
17
+ from fastapi_fastkit.utils.logging import debug_log
18
+
19
+ from .context import InspectionContext
20
+ from .docker import DockerCompose
21
+ from .fsutils import fix_all_script_line_endings, fix_script_line_endings
22
+
23
+ TEST_TIMEOUT = 300
24
+ DEFAULT_TEST_COMMAND = "pytest tests/ -v"
25
+
26
+
27
+ def detect_package_manager(project_dir: str) -> str:
28
+ """Pick the package manager that fits the project's dependency files."""
29
+ if os.path.exists(os.path.join(project_dir, "pyproject.toml")):
30
+ return "uv"
31
+ return "pip"
32
+
33
+
34
+ def prepare_environment(ctx: InspectionContext) -> bool:
35
+ """Create the inspection venv and install the project's dependencies.
36
+
37
+ Idempotent: a context that already carries a ``venv_path`` is left alone,
38
+ so the smoke test can reuse whatever a test strategy already built.
39
+ """
40
+ if ctx.venv_path:
41
+ return True
42
+
43
+ try:
44
+ venv_path = create_venv(ctx.temp_dir)
45
+ except Exception as e:
46
+ ctx.add_error(f"Failed to create virtual environment: {e}")
47
+ return False
48
+
49
+ package_manager = detect_package_manager(ctx.temp_dir)
50
+ debug_log(f"Using package manager: {package_manager}", "info")
51
+
52
+ try:
53
+ install_dependencies_with_manager(ctx.temp_dir, venv_path, package_manager)
54
+ except Exception as e:
55
+ ctx.add_error(f"Failed to install dependencies: {str(e)}")
56
+ return False
57
+
58
+ ctx.venv_path = venv_path
59
+ return True
60
+
61
+
62
+ class TestStrategy(ABC):
63
+ """Base class for the ways a template's test suite can be executed."""
64
+
65
+ name = "base"
66
+
67
+ def __init__(self, ctx: InspectionContext):
68
+ self.ctx = ctx
69
+
70
+ @property
71
+ def config(self) -> Dict[str, Any]:
72
+ """The template's configuration mapping (empty when absent)."""
73
+ return self.ctx.template_config or {}
74
+
75
+ @abstractmethod
76
+ def run(self) -> bool:
77
+ """Execute the template's tests, recording errors on the context."""
78
+
79
+ def _report_failure(self, result: subprocess.CompletedProcess[str]) -> None:
80
+ """Turn a failed test process into a single descriptive error entry."""
81
+ message = f"{self.name} tests failed with return code {result.returncode}\n"
82
+ if result.stderr:
83
+ message += f"STDERR:\n{result.stderr}\n"
84
+ if result.stdout:
85
+ message += f"STDOUT:\n{result.stdout}\n"
86
+ self.ctx.add_error(message)
87
+
88
+
89
+ class LocalVenvStrategy(TestStrategy):
90
+ """Run the tests in a virtual environment created next to the project."""
91
+
92
+ name = "Standard"
93
+
94
+ def environment(self) -> Dict[str, str]:
95
+ """Environment variables for the test process."""
96
+ return os.environ.copy()
97
+
98
+ def test_command(self) -> List[str]:
99
+ """The pytest invocation used when the template ships no test script."""
100
+ return DEFAULT_TEST_COMMAND.split()
101
+
102
+ def on_success(self) -> None:
103
+ """Hook for strategies that want to annotate a successful run."""
104
+
105
+ def run(self) -> bool:
106
+ if not prepare_environment(self.ctx):
107
+ return False
108
+
109
+ venv_path = self.ctx.venv_path
110
+ assert venv_path is not None # prepare_environment guarantees this
111
+
112
+ env = self.environment()
113
+ bin_dir = "Scripts" if os.name == "nt" else "bin"
114
+ separator = ";" if os.name == "nt" else ":"
115
+ env["PATH"] = (
116
+ f"{os.path.join(venv_path, bin_dir)}{separator}{env.get('PATH', '')}"
117
+ )
118
+
119
+ test_script_path = self.ctx.temp_path("scripts", "test.sh")
120
+ try:
121
+ if os.path.exists(test_script_path):
122
+ debug_log("Found scripts/test.sh, using template test script", "info")
123
+ result = self._run_test_script(test_script_path, env)
124
+ else:
125
+ debug_log("No scripts/test.sh found, running pytest directly", "info")
126
+ result = self._run_pytest(env)
127
+ except subprocess.TimeoutExpired:
128
+ self.ctx.add_error(f"{self.name} tests timed out after 5 minutes")
129
+ return False
130
+ except OSError as e:
131
+ self.ctx.add_error(f"Error running {self.name.lower()} tests: {e}")
132
+ return False
133
+
134
+ if result.returncode != 0:
135
+ self._report_failure(result)
136
+ return False
137
+
138
+ debug_log(f"{self.name} tests passed successfully", "info")
139
+ self.on_success()
140
+ return True
141
+
142
+ def _run_test_script(
143
+ self, test_script_path: str, env: Dict[str, str]
144
+ ) -> subprocess.CompletedProcess[str]:
145
+ """Run the template's own test script."""
146
+ fix_script_line_endings(test_script_path)
147
+ os.chmod(test_script_path, 0o755)
148
+ return subprocess.run(
149
+ [test_script_path],
150
+ cwd=self.ctx.temp_dir,
151
+ capture_output=True,
152
+ text=True,
153
+ env=env,
154
+ timeout=TEST_TIMEOUT,
155
+ )
156
+
157
+ def _run_pytest(self, env: Dict[str, str]) -> subprocess.CompletedProcess[str]:
158
+ """Run pytest with the strategy's command inside the inspection venv."""
159
+ python_executable = self.ctx.python_executable()
160
+ assert python_executable is not None
161
+ return subprocess.run(
162
+ [python_executable, "-m", *self.test_command()],
163
+ cwd=self.ctx.temp_dir,
164
+ capture_output=True,
165
+ text=True,
166
+ env=env,
167
+ timeout=TEST_TIMEOUT,
168
+ )
169
+
170
+
171
+ class StandardStrategy(LocalVenvStrategy):
172
+ """Plain virtual environment run - the default for most templates."""
173
+
174
+ name = "Standard"
175
+
176
+
177
+ class FallbackStrategy(LocalVenvStrategy):
178
+ """Docker-free substitute run (e.g. SQLite in place of PostgreSQL)."""
179
+
180
+ name = "Fallback"
181
+
182
+ @property
183
+ def fallback_config(self) -> Dict[str, Any]:
184
+ """The ``fallback_testing`` block of the template configuration."""
185
+ block = self.config.get("fallback_testing", {})
186
+ return block if isinstance(block, dict) else {}
187
+
188
+ def environment(self) -> Dict[str, str]:
189
+ env = os.environ.copy()
190
+ database_url = self.fallback_config.get("database_url", "sqlite:///:memory:")
191
+ env["DATABASE_URL"] = database_url
192
+ env["SQLALCHEMY_DATABASE_URI"] = database_url
193
+ return env
194
+
195
+ def test_command(self) -> List[str]:
196
+ command = self.fallback_config.get("test_command", DEFAULT_TEST_COMMAND)
197
+ return str(command).split()
198
+
199
+ def on_success(self) -> None:
200
+ self.ctx.add_warning(
201
+ "Tests passed using fallback strategy (SQLite instead of PostgreSQL)"
202
+ )
203
+
204
+
205
+ class DockerStrategy(TestStrategy):
206
+ """Run the tests inside the template's own Docker Compose stack."""
207
+
208
+ name = "Docker"
209
+
210
+ def _testing_config(self) -> Dict[str, Any]:
211
+ block = self.config.get("testing", {})
212
+ return block if isinstance(block, dict) else {}
213
+
214
+ def _setup_test_environment(self) -> None:
215
+ """Materialise a .env file from ``test_env_defaults`` and fix scripts."""
216
+ env_defaults = self.config.get("test_env_defaults", {})
217
+ env_file_path = self.ctx.temp_path(".env")
218
+
219
+ existing_vars: Dict[str, str] = {}
220
+ if os.path.exists(env_file_path):
221
+ debug_log(f".env file already exists at {env_file_path}", "info")
222
+ try:
223
+ with open(env_file_path, encoding="utf-8") as f:
224
+ for line in f:
225
+ line = line.strip()
226
+ if line and "=" in line and not line.startswith("#"):
227
+ key, value = line.split("=", 1)
228
+ existing_vars[key] = value
229
+ except (OSError, UnicodeDecodeError) as e:
230
+ debug_log(f"Error reading existing .env file: {e}", "warning")
231
+
232
+ final_vars = {**env_defaults, **existing_vars}
233
+ with open(env_file_path, "w", encoding="utf-8") as f:
234
+ for key, value in final_vars.items():
235
+ f.write(f"{key}={value}\n")
236
+
237
+ debug_log(
238
+ f"Set up environment file: {env_file_path} "
239
+ f"with variables: {list(final_vars.keys())}",
240
+ "info",
241
+ )
242
+ fix_all_script_line_endings(self.ctx.temp_dir)
243
+
244
+ def run(self) -> bool:
245
+ if not DockerCompose.is_available():
246
+ debug_log("Docker not available, trying fallback strategy", "warning")
247
+ self.ctx.add_warning(
248
+ "Docker not available, using fallback testing strategy"
249
+ )
250
+ return select_fallback_strategy(self.ctx).run()
251
+
252
+ testing_config = self._testing_config()
253
+ compose = DockerCompose(
254
+ self.ctx.temp_dir,
255
+ str(testing_config.get("compose_file", "docker-compose.yml")),
256
+ )
257
+ timeout = int(testing_config.get("health_check_timeout", 120))
258
+
259
+ try:
260
+ self._setup_test_environment()
261
+
262
+ if not compose.containers_running():
263
+ debug_log("Starting Docker Compose services for testing", "info")
264
+ result = compose.up(timeout)
265
+ if result.returncode != 0:
266
+ self.ctx.add_error(
267
+ f"Failed to start Docker services: {result.stderr}"
268
+ )
269
+ return False
270
+ compose.wait_until_healthy(timeout)
271
+ else:
272
+ debug_log(
273
+ "Docker Compose services already running, skipping startup", "info"
274
+ )
275
+
276
+ verification_error = compose.verify_services_running()
277
+ if verification_error is not None:
278
+ self.ctx.add_error(verification_error)
279
+ return False
280
+
281
+ return self._run_tests(compose)
282
+ except subprocess.TimeoutExpired:
283
+ self.ctx.add_error("Docker Compose setup timed out")
284
+ return False
285
+ except OSError as e:
286
+ self.ctx.add_error(f"Unexpected error during Docker testing: {e}")
287
+ return False
288
+ finally:
289
+ compose.cleanup()
290
+
291
+ def _run_tests(self, compose: DockerCompose) -> bool:
292
+ """Execute the suite inside the app container and grade the result."""
293
+ test_script_path = self.ctx.temp_path("scripts", "test.sh")
294
+ use_test_script = os.path.exists(test_script_path)
295
+ if use_test_script:
296
+ fix_script_line_endings(test_script_path)
297
+
298
+ result = compose.exec_tests(use_test_script)
299
+ if result is None:
300
+ self.ctx.add_error("Docker tests timed out")
301
+ return False
302
+
303
+ if result.returncode != 0:
304
+ self._report_failure(result)
305
+ return False
306
+
307
+ debug_log("Docker tests passed successfully", "info")
308
+ return True
309
+
310
+
311
+ def select_fallback_strategy(ctx: InspectionContext) -> TestStrategy:
312
+ """Pick the fallback strategy, or the standard one when none is configured."""
313
+ config = ctx.template_config or {}
314
+ if "fallback_testing" not in config:
315
+ debug_log("No fallback strategy configured, using standard strategy", "info")
316
+ return StandardStrategy(ctx)
317
+ return FallbackStrategy(ctx)
318
+
319
+
320
+ def select_strategy(ctx: InspectionContext) -> TestStrategy:
321
+ """Pick the strategy the template's configuration asks for."""
322
+ config = ctx.template_config or {}
323
+ if config.get("requires_docker", False):
324
+ return DockerStrategy(ctx)
325
+ return StandardStrategy(ctx)
326
+
327
+
328
+ def run_template_tests(ctx: InspectionContext) -> bool:
329
+ """Run the template's test suite with the appropriate strategy."""
330
+ if not ctx.options.run_template_tests:
331
+ debug_log("Template test execution disabled", "info")
332
+ return prepare_environment(ctx)
333
+
334
+ strategy = select_strategy(ctx)
335
+ debug_log(f"Running tests with {strategy.name} strategy", "info")
336
+ return strategy.run()