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