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
@@ -1 +1 @@
1
- __version__ = 'v1.3.0'
1
+ __version__ = 'v1.4.1'
@@ -0,0 +1,26 @@
1
+ # --------------------------------------------------------------------------
2
+ # Template inspection package.
3
+ #
4
+ # ``TemplateInspector`` generates a throwaway project from a template and runs
5
+ # the checks defined in the sibling modules against it:
6
+ #
7
+ # - ``checks`` : structure, extensions, dependencies, implementation, tests
8
+ # - ``consistency`` : Python-version pins and dependency drift
9
+ # - ``lint`` : compileall (required) and mypy (opt-in)
10
+ # - ``smoke`` : boots the generated app and probes /docs and /health
11
+ # - ``strategies`` : how the template's own test suite is executed
12
+ # - ``docker`` : Docker Compose orchestration
13
+ # - ``freshness`` : PyPI version-lag warnings
14
+ # - ``report`` : report construction and printing
15
+ #
16
+ # @author bnbong
17
+ # --------------------------------------------------------------------------
18
+ from .context import InspectionContext, InspectionOptions
19
+ from .core import TemplateInspector, inspect_fastapi_template
20
+
21
+ __all__ = [
22
+ "InspectionContext",
23
+ "InspectionOptions",
24
+ "TemplateInspector",
25
+ "inspect_fastapi_template",
26
+ ]
@@ -0,0 +1,349 @@
1
+ # --------------------------------------------------------------------------
2
+ # Static template checks: structure, file extensions, declared dependencies,
3
+ # FastAPI implementation and the mandatory test suite.
4
+ #
5
+ # Every function takes an ``InspectionContext`` and returns ``True`` when the
6
+ # check passes, appending a descriptive message to ``ctx.errors`` otherwise.
7
+ #
8
+ # @author bnbong
9
+ # --------------------------------------------------------------------------
10
+ import ast
11
+ import os
12
+ import tomllib
13
+ from pathlib import Path
14
+ from typing import List, Optional, Set, Tuple
15
+
16
+ from fastapi_fastkit.backend.main import (
17
+ _parse_setup_dependencies,
18
+ find_template_core_modules,
19
+ )
20
+ from fastapi_fastkit.backend.package_managers.poetry_manager import (
21
+ _parse_pip_requirement,
22
+ )
23
+ from fastapi_fastkit.utils.logging import debug_log
24
+
25
+ from .context import InspectionContext
26
+
27
+ ALWAYS_REQUIRED_PATHS = ["tests", "README.md-tpl"]
28
+ METADATA_CANDIDATES = ["pyproject.toml-tpl", "setup.py-tpl"]
29
+
30
+
31
+ def extract_pyproject_dependency_names(
32
+ pyproject_path: Path,
33
+ ) -> Tuple[Set[str], Optional[str]]:
34
+ """Parse a pyproject file and return (lowercased dep names, error).
35
+
36
+ ``error`` is ``None`` on success. Returns an empty set with a descriptive
37
+ error string when the file is unreadable or malformed.
38
+ """
39
+ try:
40
+ with open(pyproject_path, "rb") as f:
41
+ data = tomllib.load(f)
42
+ except (OSError, tomllib.TOMLDecodeError, UnicodeDecodeError) as e:
43
+ return set(), f"Invalid pyproject.toml-tpl: {e}"
44
+
45
+ project = data.get("project", {})
46
+ raw_deps = project.get("dependencies", []) or []
47
+ if not isinstance(raw_deps, list):
48
+ return set(), "pyproject.toml-tpl [project].dependencies must be a list"
49
+
50
+ names: Set[str] = set()
51
+ for dep in raw_deps:
52
+ if not isinstance(dep, str) or not dep.strip():
53
+ continue
54
+ name = _parse_pip_requirement(dep)[0]
55
+ if name:
56
+ names.add(name.lower())
57
+ return names, None
58
+
59
+
60
+ def parse_requirements_names(requirements_path: Path) -> Set[str]:
61
+ """Return the lowercased package names declared in a requirements file."""
62
+ with open(requirements_path, encoding="utf-8") as f:
63
+ lines = f.read().splitlines()
64
+ return {
65
+ _parse_pip_requirement(line)[0].lower()
66
+ for line in lines
67
+ if line.strip() and not line.strip().startswith("#")
68
+ }
69
+
70
+
71
+ def check_file_structure(ctx: InspectionContext) -> bool:
72
+ """Check the required file and directory structure.
73
+
74
+ Modern templates may ship ``pyproject.toml-tpl`` as the primary metadata
75
+ file; ``setup.py-tpl`` remains accepted for backward compatibility.
76
+ A template must provide at least one of the two. ``requirements.txt-tpl``
77
+ is no longer strictly required when ``pyproject.toml-tpl`` declares
78
+ ``[project].dependencies``.
79
+ """
80
+ missing_required = [
81
+ path
82
+ for path in ALWAYS_REQUIRED_PATHS
83
+ if not (ctx.template_path / path).exists()
84
+ ]
85
+ has_metadata = any(
86
+ (ctx.template_path / name).exists() for name in METADATA_CANDIDATES
87
+ )
88
+
89
+ for path in missing_required:
90
+ ctx.add_error(f"Missing required path: {path}")
91
+ if not has_metadata:
92
+ ctx.add_error(
93
+ "Missing metadata file: expected pyproject.toml-tpl "
94
+ "(preferred) or setup.py-tpl"
95
+ )
96
+
97
+ if missing_required or not has_metadata:
98
+ return False
99
+
100
+ debug_log("File structure check passed", "info")
101
+ return True
102
+
103
+
104
+ def check_file_extensions(ctx: InspectionContext) -> bool:
105
+ """Check all Python files in the template carry the .py-tpl extension."""
106
+ for path in ctx.template_path.rglob("*"):
107
+ if path.is_file() and path.suffix == ".py":
108
+ ctx.add_error(f"Found .py file instead of .py-tpl: {path}")
109
+ return False
110
+
111
+ debug_log("File extension check passed", "info")
112
+ return True
113
+
114
+
115
+ def check_dependencies(ctx: InspectionContext) -> bool:
116
+ """Check that FastAPI is declared in at least one supported source.
117
+
118
+ All three metadata sources are consulted independently:
119
+
120
+ - ``requirements.txt-tpl``
121
+ - ``pyproject.toml-tpl`` ``[project].dependencies``
122
+ - ``setup.py-tpl`` ``install_requires``
123
+
124
+ The check passes if *any* source declares ``fastapi``, so a template
125
+ with a stale ``requirements.txt-tpl`` still passes when
126
+ ``pyproject.toml-tpl`` is authoritative.
127
+ """
128
+ req_path = ctx.template_path / "requirements.txt-tpl"
129
+ pyproject_path = ctx.template_path / "pyproject.toml-tpl"
130
+ setup_path = ctx.template_path / "setup.py-tpl"
131
+
132
+ sources_checked: List[str] = []
133
+ fastapi_declared = False
134
+
135
+ if req_path.exists():
136
+ sources_checked.append("requirements.txt-tpl")
137
+ try:
138
+ package_names = parse_requirements_names(req_path)
139
+ except (OSError, UnicodeDecodeError) as e:
140
+ ctx.add_error(f"Error reading requirements.txt-tpl: {e}")
141
+ return False
142
+ debug_log(
143
+ f"requirements.txt-tpl dependencies: {sorted(package_names)}", "debug"
144
+ )
145
+ fastapi_declared = fastapi_declared or "fastapi" in package_names
146
+
147
+ if pyproject_path.exists():
148
+ sources_checked.append("pyproject.toml-tpl")
149
+ package_names, parse_error = extract_pyproject_dependency_names(pyproject_path)
150
+ if parse_error is not None:
151
+ ctx.add_error(parse_error)
152
+ return False
153
+ debug_log(f"pyproject.toml-tpl dependencies: {sorted(package_names)}", "debug")
154
+ fastapi_declared = fastapi_declared or "fastapi" in package_names
155
+
156
+ if setup_path.exists():
157
+ sources_checked.append("setup.py-tpl")
158
+ try:
159
+ with open(setup_path, encoding="utf-8") as f:
160
+ content = f.read()
161
+ except (OSError, UnicodeDecodeError) as e:
162
+ ctx.add_error(f"Error reading setup.py-tpl: {e}")
163
+ return False
164
+ package_names = {
165
+ _parse_pip_requirement(dep)[0].lower()
166
+ for dep in _parse_setup_dependencies(content)
167
+ if dep
168
+ }
169
+ debug_log(f"setup.py-tpl dependencies: {sorted(package_names)}", "debug")
170
+ fastapi_declared = fastapi_declared or "fastapi" in package_names
171
+
172
+ if not sources_checked:
173
+ ctx.add_error(
174
+ "No dependency source found: expected one of requirements.txt-tpl, "
175
+ "pyproject.toml-tpl, or setup.py-tpl"
176
+ )
177
+ return False
178
+
179
+ if not fastapi_declared:
180
+ ctx.add_error(
181
+ "FastAPI dependency not found in any source ("
182
+ + ", ".join(sources_checked)
183
+ + ")"
184
+ )
185
+ return False
186
+
187
+ debug_log(
188
+ f"Dependencies check passed (sources: {', '.join(sources_checked)})", "info"
189
+ )
190
+ return True
191
+
192
+
193
+ def _module_defines_fastapi_app(source: str) -> bool:
194
+ """Return True when ``source`` assigns a ``FastAPI(...)`` instance."""
195
+ try:
196
+ tree = ast.parse(source)
197
+ except SyntaxError:
198
+ return False
199
+
200
+ for node in ast.walk(tree):
201
+ if not isinstance(node, (ast.Assign, ast.AnnAssign)):
202
+ continue
203
+ value = node.value
204
+ if not isinstance(value, ast.Call):
205
+ continue
206
+ func = value.func
207
+ name = ""
208
+ if isinstance(func, ast.Name):
209
+ name = func.id
210
+ elif isinstance(func, ast.Attribute):
211
+ name = func.attr
212
+ if name == "FastAPI":
213
+ return True
214
+ return False
215
+
216
+
217
+ def check_fastapi_implementation(ctx: InspectionContext) -> bool:
218
+ """Check the generated project really instantiates a FastAPI application.
219
+
220
+ This is a static (AST) guard so a broken main module is reported before
221
+ the far more expensive smoke test boots a server.
222
+ """
223
+ try:
224
+ core_modules = find_template_core_modules(ctx.temp_dir)
225
+ debug_log(f"Found core modules: {core_modules}", "debug")
226
+
227
+ if not core_modules["main"]:
228
+ ctx.add_error("main.py not found in template")
229
+ return False
230
+
231
+ with open(core_modules["main"], encoding="utf-8") as f:
232
+ content = f.read()
233
+ except (OSError, UnicodeDecodeError) as e:
234
+ ctx.add_error(f"Error checking FastAPI implementation: {e}")
235
+ return False
236
+
237
+ if not _module_defines_fastapi_app(content):
238
+ ctx.add_error("FastAPI app creation not found in main.py")
239
+ debug_log(f"main.py content preview: {content[:200]}...", "debug")
240
+ return False
241
+
242
+ debug_log("FastAPI implementation check passed", "info")
243
+ return True
244
+
245
+
246
+ def check_tests_present(ctx: InspectionContext) -> bool:
247
+ """A template must ship at least one test module.
248
+
249
+ Previously a missing ``tests/`` directory only produced a warning; an
250
+ untested template is now a hard failure.
251
+ """
252
+ tests_dir = ctx.template_path / "tests"
253
+ if not tests_dir.is_dir():
254
+ ctx.add_error("Missing required path: tests (templates must ship tests)")
255
+ return False
256
+
257
+ test_files = [
258
+ path
259
+ for path in tests_dir.rglob("*")
260
+ if path.is_file()
261
+ and path.name.startswith("test_")
262
+ and path.name.endswith((".py-tpl", ".py"))
263
+ ]
264
+ if not test_files:
265
+ ctx.add_error(
266
+ "No test module found under tests/: at least one test_*.py-tpl file "
267
+ "is required"
268
+ )
269
+ return False
270
+
271
+ debug_log(f"Test suite check passed ({len(test_files)} test files)", "info")
272
+ return True
273
+
274
+
275
+ def check_no_junk_files(ctx: InspectionContext) -> bool:
276
+ """Reject OS/editor artifacts and build leftovers committed to a template."""
277
+ junk_names = {".DS_Store", "Thumbs.db", "desktop.ini", ".AppleDouble"}
278
+ junk_dirs = {"__pycache__", ".pytest_cache", ".mypy_cache", ".venv"}
279
+ found: List[str] = []
280
+
281
+ for path in ctx.template_path.rglob("*"):
282
+ rel = path.relative_to(ctx.template_path)
283
+ if path.is_dir():
284
+ if path.name in junk_dirs:
285
+ found.append(str(rel))
286
+ elif path.name in junk_names or path.suffix in {".pyc", ".pyo"}:
287
+ found.append(str(rel))
288
+
289
+ if found:
290
+ for item in sorted(found):
291
+ ctx.add_error(f"Junk file committed to template: {item}")
292
+ return False
293
+
294
+ debug_log("Junk file check passed", "info")
295
+ return True
296
+
297
+
298
+ def check_no_placeholder_residue(ctx: InspectionContext) -> bool:
299
+ """Every ``<placeholder>`` must be substituted during project generation.
300
+
301
+ The inspection generates a real project from the template (copy +
302
+ metadata injection), so any surviving placeholder token means the
303
+ template declares a variable the generator does not know how to fill.
304
+ """
305
+ placeholders = [
306
+ "<project_name>",
307
+ "<author>",
308
+ "<author_email>",
309
+ "<description>",
310
+ ]
311
+ text_suffixes = {
312
+ ".py",
313
+ ".toml",
314
+ ".cfg",
315
+ ".txt",
316
+ ".md",
317
+ ".yml",
318
+ ".yaml",
319
+ ".json",
320
+ ".sh",
321
+ ".env",
322
+ ".ini",
323
+ }
324
+ residue: List[str] = []
325
+
326
+ for root, dirs, files in os.walk(ctx.temp_dir):
327
+ dirs[:] = [d for d in dirs if d not in {".venv", "venv", "__pycache__", ".git"}]
328
+ for file_name in files:
329
+ file_path = os.path.join(root, file_name)
330
+ suffix = Path(file_name).suffix
331
+ if suffix and suffix not in text_suffixes:
332
+ continue
333
+ try:
334
+ with open(file_path, encoding="utf-8") as f:
335
+ content = f.read()
336
+ except (OSError, UnicodeDecodeError):
337
+ continue
338
+ hits = [token for token in placeholders if token in content]
339
+ if hits:
340
+ rel = os.path.relpath(file_path, ctx.temp_dir)
341
+ residue.append(f"{rel}: {', '.join(hits)}")
342
+
343
+ if residue:
344
+ for item in sorted(residue):
345
+ ctx.add_error(f"Unsubstituted placeholder in generated project: {item}")
346
+ return False
347
+
348
+ debug_log("Placeholder substitution check passed", "info")
349
+ return True
@@ -0,0 +1,125 @@
1
+ # --------------------------------------------------------------------------
2
+ # Configuration consistency checks for a template's metadata files.
3
+ #
4
+ # FastAPI-fastkit pins generated projects to Python 3.12, so every tool that
5
+ # carries a Python version must agree. The dependency drift check keeps
6
+ # ``pyproject.toml-tpl`` and ``requirements.txt-tpl`` from diverging, and the
7
+ # self-dependency check keeps fastkit itself out of generated runtimes.
8
+ #
9
+ # @author bnbong
10
+ # --------------------------------------------------------------------------
11
+ import tomllib
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ from fastapi_fastkit.utils.logging import debug_log
16
+
17
+ from .checks import extract_pyproject_dependency_names, parse_requirements_names
18
+ from .context import InspectionContext
19
+
20
+ TARGET_PYTHON = "3.12"
21
+ TARGET_PYTHON_TAG = "py312"
22
+ #: fastkit is a scaffolding tool - a generated project must not depend on it.
23
+ SELF_PACKAGE_NAME = "fastapi-fastkit"
24
+
25
+
26
+ def _load_pyproject(pyproject_path: Path) -> Optional[Dict[str, Any]]:
27
+ """Load a pyproject template file, returning ``None`` when unreadable."""
28
+ try:
29
+ with open(pyproject_path, "rb") as f:
30
+ data: Dict[str, Any] = tomllib.load(f)
31
+ return data
32
+ except (OSError, tomllib.TOMLDecodeError, UnicodeDecodeError) as e:
33
+ debug_log(f"Failed to parse {pyproject_path}: {e}", "warning")
34
+ return None
35
+
36
+
37
+ def _check_python_versions(ctx: InspectionContext, data: Dict[str, Any]) -> bool:
38
+ """requires-python / black target-version / mypy python_version must be 3.12."""
39
+ passed = True
40
+ tools = data.get("tool", {})
41
+
42
+ requires_python = data.get("project", {}).get("requires-python")
43
+ if requires_python is None:
44
+ ctx.add_error("pyproject.toml-tpl is missing [project].requires-python")
45
+ passed = False
46
+ elif TARGET_PYTHON not in str(requires_python):
47
+ ctx.add_error(
48
+ f"requires-python must target Python {TARGET_PYTHON}, "
49
+ f"found {requires_python!r}"
50
+ )
51
+ passed = False
52
+
53
+ black_target = tools.get("black", {}).get("target-version")
54
+ if black_target is not None:
55
+ targets = black_target if isinstance(black_target, list) else [black_target]
56
+ if TARGET_PYTHON_TAG not in targets:
57
+ ctx.add_error(
58
+ f"[tool.black].target-version must include {TARGET_PYTHON_TAG!r}, "
59
+ f"found {black_target!r}"
60
+ )
61
+ passed = False
62
+
63
+ mypy_version = tools.get("mypy", {}).get("python_version")
64
+ if mypy_version is not None and str(mypy_version) != TARGET_PYTHON:
65
+ ctx.add_error(
66
+ f"[tool.mypy].python_version must be {TARGET_PYTHON!r}, "
67
+ f"found {mypy_version!r}"
68
+ )
69
+ passed = False
70
+
71
+ return passed
72
+
73
+
74
+ def _check_self_dependency(ctx: InspectionContext, names: set[str]) -> bool:
75
+ """Generated projects must not ship FastAPI-fastkit as a runtime dependency."""
76
+ if SELF_PACKAGE_NAME in {name.lower() for name in names}:
77
+ ctx.add_error(
78
+ "FastAPI-fastkit must not be a runtime dependency of a generated "
79
+ "project (remove it from the template's dependency list)"
80
+ )
81
+ return False
82
+ return True
83
+
84
+
85
+ def check_configuration_consistency(ctx: InspectionContext) -> bool:
86
+ """Validate Python-version pins, dependency drift and self-dependency."""
87
+ pyproject_path = ctx.template_path / "pyproject.toml-tpl"
88
+ requirements_path = ctx.template_path / "requirements.txt-tpl"
89
+ passed = True
90
+
91
+ pyproject_deps: set[str] = set()
92
+ if pyproject_path.exists():
93
+ data = _load_pyproject(pyproject_path)
94
+ if data is None:
95
+ ctx.add_error("Invalid pyproject.toml-tpl: could not be parsed")
96
+ return False
97
+ passed = _check_python_versions(ctx, data) and passed
98
+
99
+ pyproject_deps, parse_error = extract_pyproject_dependency_names(pyproject_path)
100
+ if parse_error is not None:
101
+ ctx.add_error(parse_error)
102
+ return False
103
+ passed = _check_self_dependency(ctx, pyproject_deps) and passed
104
+
105
+ requirement_names: set[str] = set()
106
+ if requirements_path.exists():
107
+ try:
108
+ requirement_names = parse_requirements_names(requirements_path)
109
+ except (OSError, UnicodeDecodeError) as e:
110
+ ctx.add_error(f"Error reading requirements.txt-tpl: {e}")
111
+ return False
112
+ passed = _check_self_dependency(ctx, requirement_names) and passed
113
+
114
+ if pyproject_deps and requirement_names:
115
+ missing: List[str] = sorted(pyproject_deps - requirement_names)
116
+ if missing:
117
+ ctx.add_error(
118
+ "Dependency drift: declared in pyproject.toml-tpl but absent from "
119
+ "requirements.txt-tpl: " + ", ".join(missing)
120
+ )
121
+ passed = False
122
+
123
+ if passed:
124
+ debug_log("Configuration consistency check passed", "info")
125
+ return passed
@@ -0,0 +1,71 @@
1
+ # --------------------------------------------------------------------------
2
+ # Shared state for the template inspection pipeline.
3
+ #
4
+ # Every check and test strategy operates on an ``InspectionContext``: it owns
5
+ # the template source path, the temporary generated-project path, the loaded
6
+ # template configuration and the accumulated errors/warnings.
7
+ #
8
+ # @author bnbong
9
+ # --------------------------------------------------------------------------
10
+ import os
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ from fastapi_fastkit.utils.logging import debug_log
16
+
17
+
18
+ @dataclass
19
+ class InspectionOptions:
20
+ """Tunable switches for a single inspection run."""
21
+
22
+ #: Skip every network access (dependency freshness lookups).
23
+ offline: bool = False
24
+ #: Boot the generated project with uvicorn and probe its HTTP endpoints.
25
+ run_smoke_test: bool = True
26
+ #: Run ``mypy`` inside the generated project (opt-in, slow).
27
+ run_mypy: bool = False
28
+ #: Run the template's own test suite.
29
+ run_template_tests: bool = True
30
+ #: Seconds to wait for the smoke-test server to answer.
31
+ smoke_timeout: int = 60
32
+
33
+
34
+ @dataclass
35
+ class InspectionContext:
36
+ """Mutable state shared by all inspection steps."""
37
+
38
+ template_path: Path
39
+ temp_dir: str
40
+ options: InspectionOptions = field(default_factory=InspectionOptions)
41
+ template_config: Optional[Dict[str, Any]] = None
42
+ errors: List[str] = field(default_factory=list)
43
+ warnings: List[str] = field(default_factory=list)
44
+ #: Populated by a test strategy so later checks can reuse the environment.
45
+ venv_path: Optional[str] = None
46
+ #: Populated when a test strategy already exercised the app's HTTP surface
47
+ #: (the Docker strategy probes the running container), so the Smoke Test
48
+ #: step reuses that verdict instead of booting a second server.
49
+ smoke_result: Optional[bool] = None
50
+
51
+ def add_error(self, message: str) -> None:
52
+ """Record a fatal finding."""
53
+ self.errors.append(message)
54
+ debug_log(f"Inspection error: {message}", "error")
55
+
56
+ def add_warning(self, message: str) -> None:
57
+ """Record a non-fatal finding."""
58
+ self.warnings.append(message)
59
+ debug_log(f"Inspection warning: {message}", "warning")
60
+
61
+ def temp_path(self, *parts: str) -> str:
62
+ """Join ``parts`` onto the generated project directory."""
63
+ return os.path.join(self.temp_dir, *parts)
64
+
65
+ def python_executable(self) -> Optional[str]:
66
+ """Return the interpreter of the inspection venv, if one was created."""
67
+ if not self.venv_path:
68
+ return None
69
+ if os.name == "nt": # pragma: no cover - Windows-only branch
70
+ return os.path.join(self.venv_path, "Scripts", "python")
71
+ return os.path.join(self.venv_path, "bin", "python")