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,145 @@
1
+ # --------------------------------------------------------------------------
2
+ # Dependency freshness reporting.
3
+ #
4
+ # Each pinned dependency is compared against the latest release published on
5
+ # PyPI. Findings are warnings only - a template lagging behind upstream is
6
+ # worth surfacing but never a reason to fail an inspection. Network failures
7
+ # are skipped silently, and ``--offline`` disables the lookups entirely.
8
+ #
9
+ # @author bnbong
10
+ # --------------------------------------------------------------------------
11
+ import json
12
+ import re
13
+ import tomllib
14
+ import urllib.error
15
+ import urllib.request
16
+ from typing import Dict, Optional, Tuple
17
+
18
+ from fastapi_fastkit.backend.package_managers.poetry_manager import (
19
+ _parse_pip_requirement,
20
+ )
21
+ from fastapi_fastkit.utils.logging import debug_log
22
+
23
+ from .context import InspectionContext
24
+
25
+ PYPI_JSON_URL = "https://pypi.org/pypi/{package}/json"
26
+ PYPI_TIMEOUT = 5
27
+ _VERSION_RE = re.compile(r"(\d+)\.(\d+)")
28
+ #: Only exact/lower-bound pins carry a meaningful "current version".
29
+ _PIN_OPERATORS = ("==", ">=", "~=")
30
+
31
+
32
+ def _pinned_version(requirement: str) -> Tuple[str, str]:
33
+ """Return ``(package name, pinned version)`` for a requirement string.
34
+
35
+ Version is empty when the requirement is unpinned or uses an operator
36
+ that does not identify a concrete current version (e.g. ``<2``).
37
+ """
38
+ name, _extras, version_spec, _marker = _parse_pip_requirement(requirement)
39
+ if not name or not version_spec:
40
+ return name.lower() if name else "", ""
41
+ for operator in _PIN_OPERATORS:
42
+ if version_spec.startswith(operator):
43
+ return name.lower(), version_spec[len(operator) :].strip()
44
+ return name.lower(), ""
45
+
46
+
47
+ def _parse_version(version: str) -> Optional[Tuple[int, int]]:
48
+ """Extract a (major, minor) pair from a version string."""
49
+ match = _VERSION_RE.match(version.strip())
50
+ if not match:
51
+ return None
52
+ return int(match.group(1)), int(match.group(2))
53
+
54
+
55
+ def fetch_latest_version(package: str) -> Optional[str]:
56
+ """Return the latest version of ``package`` on PyPI, or ``None`` on failure."""
57
+ url = PYPI_JSON_URL.format(package=package)
58
+ try:
59
+ with urllib.request.urlopen(url, timeout=PYPI_TIMEOUT) as response:
60
+ payload = json.loads(response.read().decode("utf-8"))
61
+ except (urllib.error.URLError, OSError, ValueError, TimeoutError) as e:
62
+ debug_log(f"PyPI lookup failed for {package}: {e}", "debug")
63
+ return None
64
+ version = payload.get("info", {}).get("version")
65
+ return str(version) if version else None
66
+
67
+
68
+ def _collect_pins(ctx: InspectionContext) -> Dict[str, str]:
69
+ """Map package name -> pinned version from the template's metadata files."""
70
+ pins: Dict[str, str] = {}
71
+
72
+ requirements_path = ctx.template_path / "requirements.txt-tpl"
73
+ if requirements_path.exists():
74
+ try:
75
+ with open(requirements_path, encoding="utf-8") as f:
76
+ lines = f.read().splitlines()
77
+ except (OSError, UnicodeDecodeError):
78
+ lines = []
79
+ for line in lines:
80
+ line = line.strip()
81
+ if not line or line.startswith("#"):
82
+ continue
83
+ name, version = _pinned_version(line)
84
+ if name and version:
85
+ pins.setdefault(name, version)
86
+
87
+ pyproject_path = ctx.template_path / "pyproject.toml-tpl"
88
+ if pyproject_path.exists():
89
+ try:
90
+ with open(pyproject_path, "rb") as f:
91
+ data = tomllib.load(f)
92
+ except (OSError, ValueError):
93
+ data = {}
94
+ for dep in data.get("project", {}).get("dependencies", []) or []:
95
+ if not isinstance(dep, str):
96
+ continue
97
+ name, version = _pinned_version(dep)
98
+ if name and version:
99
+ pins.setdefault(name, version)
100
+
101
+ return pins
102
+
103
+
104
+ def check_dependency_freshness(ctx: InspectionContext) -> bool:
105
+ """Warn about dependencies that lag behind their latest PyPI release.
106
+
107
+ Always returns ``True``: freshness is advisory.
108
+ """
109
+ if ctx.options.offline:
110
+ debug_log("Offline mode - skipping dependency freshness check", "info")
111
+ return True
112
+
113
+ pins = _collect_pins(ctx)
114
+ if not pins:
115
+ debug_log("No pinned dependencies found for freshness check", "info")
116
+ return True
117
+
118
+ checked = 0
119
+ for package, pinned in sorted(pins.items()):
120
+ latest = fetch_latest_version(package)
121
+ if latest is None:
122
+ continue
123
+ checked += 1
124
+ current_parts = _parse_version(pinned)
125
+ latest_parts = _parse_version(latest)
126
+ if current_parts is None or latest_parts is None:
127
+ continue
128
+ if latest_parts[0] > current_parts[0]:
129
+ ctx.add_warning(
130
+ f"{package} is {latest_parts[0] - current_parts[0]} major "
131
+ f"version(s) behind: pinned {pinned}, latest {latest}"
132
+ )
133
+ elif latest_parts[0] == current_parts[0] and latest_parts[1] > current_parts[1]:
134
+ ctx.add_warning(
135
+ f"{package} is {latest_parts[1] - current_parts[1]} minor "
136
+ f"version(s) behind: pinned {pinned}, latest {latest}"
137
+ )
138
+
139
+ if checked == 0:
140
+ debug_log(
141
+ "Dependency freshness check skipped - no PyPI responses received", "info"
142
+ )
143
+ else:
144
+ debug_log(f"Dependency freshness check completed ({checked} packages)", "info")
145
+ return True
@@ -0,0 +1,139 @@
1
+ # --------------------------------------------------------------------------
2
+ # Filesystem helpers shared by the inspection pipeline: shell script line
3
+ # ending normalisation and best-effort removal of temporary directories that
4
+ # Docker or a test run may have left behind with awkward permissions.
5
+ #
6
+ # @author bnbong
7
+ # --------------------------------------------------------------------------
8
+ import os
9
+ import shutil
10
+ import stat
11
+ import subprocess
12
+ import time
13
+
14
+ from fastapi_fastkit.utils.logging import debug_log
15
+
16
+ CLEANUP_MAX_RETRIES = 5
17
+
18
+
19
+ def fix_script_line_endings(script_path: str) -> None:
20
+ """Convert Windows line endings in a script to Unix ones."""
21
+ try:
22
+ with open(script_path, "rb") as f:
23
+ content = f.read()
24
+
25
+ content = content.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
26
+
27
+ with open(script_path, "wb") as f:
28
+ f.write(content)
29
+
30
+ debug_log(f"Fixed line endings in {script_path}", "debug")
31
+ except OSError as e:
32
+ debug_log(f"Failed to fix line endings in {script_path}: {e}", "warning")
33
+
34
+
35
+ def fix_all_script_line_endings(directory: str) -> None:
36
+ """Normalise line endings for every shell script below ``directory``."""
37
+ for root, _dirs, files in os.walk(directory):
38
+ for file_name in files:
39
+ if file_name.endswith((".sh", ".bash")):
40
+ fix_script_line_endings(os.path.join(root, file_name))
41
+
42
+ debug_log("Fixed line endings for all shell scripts", "info")
43
+
44
+
45
+ def _fix_permissions(path: str) -> None:
46
+ """Grant full permissions to ``path``, ignoring failures."""
47
+ try:
48
+ os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
49
+ except OSError:
50
+ pass
51
+
52
+
53
+ def fix_directory_permissions(directory_path: str) -> None:
54
+ """Make a directory tree writable so it can be removed."""
55
+ _fix_permissions(directory_path)
56
+ for root, dirs, files in os.walk(directory_path):
57
+ _fix_permissions(root)
58
+ for name in dirs + files:
59
+ _fix_permissions(os.path.join(root, name))
60
+
61
+
62
+ def remove_directory_contents(directory_path: str) -> None:
63
+ """Remove a directory's contents entry by entry, ignoring failures."""
64
+ for root, dirs, files in os.walk(directory_path, topdown=False):
65
+ for file_name in files:
66
+ file_path = os.path.join(root, file_name)
67
+ _fix_permissions(file_path)
68
+ try:
69
+ os.remove(file_path)
70
+ except OSError:
71
+ pass
72
+
73
+ for dir_name in dirs:
74
+ dir_path = os.path.join(root, dir_name)
75
+ _fix_permissions(dir_path)
76
+ try:
77
+ os.rmdir(dir_path)
78
+ except OSError:
79
+ pass
80
+
81
+
82
+ def force_cleanup_directory(directory_path: str) -> None:
83
+ """Remove ``directory_path`` using progressively more forceful strategies."""
84
+ for attempt in range(CLEANUP_MAX_RETRIES):
85
+ try:
86
+ shutil.rmtree(directory_path)
87
+ debug_log(
88
+ f"Successfully cleaned up temp directory: {directory_path}", "info"
89
+ )
90
+ return
91
+ except (OSError, PermissionError) as e:
92
+ if attempt == CLEANUP_MAX_RETRIES - 1:
93
+ break
94
+ debug_log(
95
+ f"Attempt {attempt + 1} failed to cleanup {directory_path}: {e}",
96
+ "warning",
97
+ )
98
+
99
+ try:
100
+ fix_directory_permissions(directory_path)
101
+ shutil.rmtree(directory_path)
102
+ debug_log(
103
+ "Successfully cleaned up temp directory after permission fix: "
104
+ f"{directory_path}",
105
+ "info",
106
+ )
107
+ return
108
+ except OSError:
109
+ pass
110
+
111
+ try:
112
+ remove_directory_contents(directory_path)
113
+ os.rmdir(directory_path)
114
+ debug_log(
115
+ "Successfully cleaned up temp directory by removing contents: "
116
+ f"{directory_path}",
117
+ "info",
118
+ )
119
+ return
120
+ except OSError:
121
+ pass
122
+
123
+ time.sleep(2)
124
+
125
+ debug_log(
126
+ f"Failed to cleanup temp directory after {CLEANUP_MAX_RETRIES} attempts: "
127
+ f"{directory_path}",
128
+ "warning",
129
+ )
130
+ try:
131
+ if os.name == "nt": # pragma: no cover - Windows-only branch
132
+ subprocess.run(
133
+ ["rmdir", "/s", "/q", directory_path], check=False, shell=True
134
+ )
135
+ else:
136
+ subprocess.run(["rm", "-rf", directory_path], check=False)
137
+ debug_log(f"Force cleanup completed for: {directory_path}", "info")
138
+ except OSError:
139
+ debug_log(f"All cleanup attempts failed for: {directory_path}", "warning")
@@ -0,0 +1,103 @@
1
+ # --------------------------------------------------------------------------
2
+ # Static analysis of the generated project.
3
+ #
4
+ # The compile check is mandatory - a template that cannot be compiled is
5
+ # broken beyond argument. It is performed in-process with ``compile()`` and
6
+ # deliberately writes nothing to disk: a Docker test run beforehand can leave
7
+ # root-owned ``__pycache__`` directories in the bind-mounted project, and
8
+ # ``compileall`` would then fail with a PermissionError that says nothing
9
+ # about the template. ``mypy`` is opt-in because it needs the template's own
10
+ # dependencies installed and is far slower.
11
+ #
12
+ # @author bnbong
13
+ # --------------------------------------------------------------------------
14
+ import os
15
+ import subprocess
16
+ import sys
17
+ from typing import List
18
+
19
+ from fastapi_fastkit.utils.logging import debug_log
20
+
21
+ from .context import InspectionContext
22
+
23
+ MYPY_TIMEOUT = 300
24
+
25
+ #: Directories never worth compiling - third party code and build artefacts.
26
+ EXCLUDED_DIRS = {".venv", "venv", "__pycache__", "node_modules"}
27
+
28
+
29
+ def _interpreter(ctx: InspectionContext) -> str:
30
+ """Prefer the inspection venv interpreter, falling back to the host one."""
31
+ return ctx.python_executable() or sys.executable
32
+
33
+
34
+ def _iter_python_files(root: str) -> List[str]:
35
+ """Collect every ``.py`` file below ``root``, skipping excluded directories."""
36
+ python_files: List[str] = []
37
+ for dirpath, dirnames, filenames in os.walk(root):
38
+ dirnames[:] = [name for name in dirnames if name not in EXCLUDED_DIRS]
39
+ for file_name in sorted(filenames):
40
+ if file_name.endswith(".py"):
41
+ python_files.append(os.path.join(dirpath, file_name))
42
+ return python_files
43
+
44
+
45
+ def check_compileall(ctx: InspectionContext) -> bool:
46
+ """Compile every Python file of the generated project, in memory.
47
+
48
+ Nothing is written to disk - no ``__pycache__`` is created - so the check
49
+ stays a pure syntax gate that cannot trip over file ownership left behind
50
+ by a containerised test run.
51
+ """
52
+ failures: List[str] = []
53
+
54
+ for path in _iter_python_files(ctx.temp_dir):
55
+ try:
56
+ with open(path, "rb") as f:
57
+ source = f.read()
58
+ except OSError as e:
59
+ failures.append(f"{path}: could not be read ({e})")
60
+ continue
61
+
62
+ try:
63
+ compile(source, path, "exec", dont_inherit=True)
64
+ except (SyntaxError, ValueError) as e:
65
+ failures.append(f"{path}: {e}")
66
+
67
+ if failures:
68
+ detail = "\n".join(failures)
69
+ ctx.add_error(f"Generated project failed to compile:\n{detail}")
70
+ return False
71
+
72
+ debug_log("compileall check passed", "info")
73
+ return True
74
+
75
+
76
+ def check_mypy(ctx: InspectionContext) -> bool:
77
+ """Run mypy inside the generated project (opt-in via ``--mypy``)."""
78
+ if not ctx.options.run_mypy:
79
+ debug_log("mypy check disabled", "info")
80
+ return True
81
+
82
+ try:
83
+ result = subprocess.run(
84
+ [_interpreter(ctx), "-m", "mypy", "."],
85
+ cwd=ctx.temp_dir,
86
+ capture_output=True,
87
+ text=True,
88
+ timeout=MYPY_TIMEOUT,
89
+ )
90
+ except subprocess.TimeoutExpired:
91
+ ctx.add_error("mypy timed out")
92
+ return False
93
+ except OSError as e:
94
+ ctx.add_warning(f"Could not run mypy: {e}")
95
+ return True
96
+
97
+ if result.returncode != 0:
98
+ detail = (result.stdout or result.stderr or "").strip()
99
+ ctx.add_error(f"mypy reported errors in the generated project:\n{detail}")
100
+ return False
101
+
102
+ debug_log("mypy check passed", "info")
103
+ return True
@@ -0,0 +1,58 @@
1
+ # --------------------------------------------------------------------------
2
+ # Inspection reporting: turn an InspectionContext into the report dictionary
3
+ # consumed by the CLI, the local scripts and the CI workflows.
4
+ #
5
+ # @author bnbong
6
+ # --------------------------------------------------------------------------
7
+ from typing import Any, Dict
8
+
9
+ from fastapi_fastkit.utils.logging import debug_log
10
+ from fastapi_fastkit.utils.main import print_error, print_success, print_warning
11
+
12
+ from .context import InspectionContext
13
+
14
+
15
+ def build_report(ctx: InspectionContext) -> Dict[str, Any]:
16
+ """Build the inspection report and log a summary of the findings."""
17
+ is_valid = len(ctx.errors) == 0
18
+ template_name = ctx.template_path.name
19
+
20
+ if is_valid:
21
+ debug_log(
22
+ f"Template inspection completed successfully for {template_name}", "info"
23
+ )
24
+ else:
25
+ debug_log(
26
+ f"Template inspection failed for {template_name} "
27
+ f"with {len(ctx.errors)} errors",
28
+ "error",
29
+ )
30
+ for index, error in enumerate(ctx.errors, 1):
31
+ debug_log(f"Error {index}: {error}", "error")
32
+
33
+ if ctx.warnings:
34
+ debug_log(
35
+ f"Template {template_name} has {len(ctx.warnings)} warnings: "
36
+ f"{ctx.warnings}",
37
+ "warning",
38
+ )
39
+
40
+ return {
41
+ "template_path": str(ctx.template_path),
42
+ "errors": list(ctx.errors),
43
+ "warnings": list(ctx.warnings),
44
+ "is_valid": is_valid,
45
+ }
46
+
47
+
48
+ def print_report(ctx: InspectionContext, is_valid: bool) -> None:
49
+ """Print the human-facing inspection outcome."""
50
+ if is_valid:
51
+ print_success(f"Template {ctx.template_path} is valid!")
52
+ else:
53
+ print_error(f"Template {ctx.template_path} validation failed")
54
+ for error in ctx.errors:
55
+ print_error(f" - {error}")
56
+
57
+ for warning in ctx.warnings:
58
+ print_warning(f" - {warning}")