fastapi-basekit 0.2.1__tar.gz → 0.3.1__tar.gz

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 (119) hide show
  1. fastapi_basekit-0.3.1/PKG-INFO +134 -0
  2. fastapi_basekit-0.3.1/README.md +88 -0
  3. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/repository/base.py +29 -5
  4. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/__init__.py +2 -1
  5. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/service/base.py +8 -1
  6. fastapi_basekit-0.3.1/fastapi_basekit/aio/sqlalchemy/session.py +67 -0
  7. fastapi_basekit-0.3.1/fastapi_basekit/cli/__init__.py +5 -0
  8. fastapi_basekit-0.3.1/fastapi_basekit/cli/main.py +112 -0
  9. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/cookiecutter.json +19 -0
  10. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/hooks/post_gen_project.py +51 -0
  11. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/hooks/pre_gen_project.py +32 -0
  12. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.env.example +40 -0
  13. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/.gitignore +12 -0
  14. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Dockerfile +18 -0
  15. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/LICENSE +61 -0
  16. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/Makefile +46 -0
  17. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/README.md +41 -0
  18. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/env.py +84 -0
  19. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic/script.py.mako +26 -0
  20. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/alembic.ini +45 -0
  21. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/__init__.py +0 -0
  22. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/__init__.py +0 -0
  23. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/__init__.py +0 -0
  24. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/__init__.py +0 -0
  25. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/__init__.py +3 -0
  26. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/auth/auth.py +42 -0
  27. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/__init__.py +3 -0
  28. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/endpoints/user/user.py +73 -0
  29. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/api/v1/routers.py +11 -0
  30. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/__init__.py +3 -0
  31. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/database.py +107 -0
  32. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/config/settings.py +70 -0
  33. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/main.py +96 -0
  34. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/__init__.py +0 -0
  35. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/auth.py +78 -0
  36. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/middleware/permissions.py +12 -0
  37. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/__init__.py +10 -0
  38. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/auth.py +49 -0
  39. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/base.py +78 -0
  40. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/enums.py +8 -0
  41. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/models/types.py +56 -0
  42. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/__init__.py +0 -0
  43. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/permissions/user.py +26 -0
  44. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/__init__.py +0 -0
  45. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/__init__.py +0 -0
  46. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/repositories/user/repository.py +36 -0
  47. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/__init__.py +0 -0
  48. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/auth.py +20 -0
  49. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/base.py +13 -0
  50. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/schemas/user.py +42 -0
  51. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/__init__.py +0 -0
  52. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init.py +13 -0
  53. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/scripts/init_admin.py +61 -0
  54. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/__init__.py +0 -0
  55. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/auth_service.py +102 -0
  56. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/dependency.py +70 -0
  57. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/services/user_service.py +46 -0
  58. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/__init__.py +0 -0
  59. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/exception_handlers.py +84 -0
  60. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/app/utils/security.py +11 -0
  61. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/docker-compose.yml +88 -0
  62. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/pytest.ini +6 -0
  63. fastapi_basekit-0.3.1/fastapi_basekit/templates/project/{{cookiecutter.project_slug}}/requirements.txt +60 -0
  64. fastapi_basekit-0.3.1/fastapi_basekit.egg-info/PKG-INFO +134 -0
  65. fastapi_basekit-0.3.1/fastapi_basekit.egg-info/SOURCES.txt +112 -0
  66. fastapi_basekit-0.3.1/fastapi_basekit.egg-info/entry_points.txt +2 -0
  67. fastapi_basekit-0.3.1/fastapi_basekit.egg-info/requires.txt +34 -0
  68. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/pyproject.toml +32 -9
  69. fastapi_basekit-0.2.1/PKG-INFO +0 -775
  70. fastapi_basekit-0.2.1/README.md +0 -739
  71. fastapi_basekit-0.2.1/fastapi_basekit.egg-info/PKG-INFO +0 -775
  72. fastapi_basekit-0.2.1/fastapi_basekit.egg-info/SOURCES.txt +0 -53
  73. fastapi_basekit-0.2.1/fastapi_basekit.egg-info/requires.txt +0 -22
  74. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/LICENSE +0 -0
  75. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/__init__.py +0 -0
  76. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/__init__.py +0 -0
  77. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/__init__.py +0 -0
  78. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/controller/__init__.py +0 -0
  79. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/controller/base.py +0 -0
  80. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/repository/__init__.py +0 -0
  81. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/service/__init__.py +0 -0
  82. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/beanie/service/base.py +0 -0
  83. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/controller/__init__.py +0 -0
  84. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/controller/base.py +0 -0
  85. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/permissions/__init__.py +0 -0
  86. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/permissions/base.py +0 -0
  87. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/controller/__init__.py +0 -0
  88. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/controller/base.py +0 -0
  89. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/repository/__init__.py +0 -0
  90. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/repository/base.py +0 -0
  91. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlalchemy/service/__init__.py +0 -0
  92. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/__init__.py +0 -0
  93. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/controller/__init__.py +0 -0
  94. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/controller/base.py +0 -0
  95. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/repository/__init__.py +0 -0
  96. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/repository/base.py +0 -0
  97. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/service/__init__.py +0 -0
  98. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/aio/sqlmodel/service/base.py +0 -0
  99. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/exceptions/__init__.py +0 -0
  100. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/exceptions/api_exceptions.py +0 -0
  101. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/exceptions/handler.py +0 -0
  102. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/schema/__init__.py +0 -0
  103. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/schema/base.py +0 -0
  104. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/schema/jwt.py +0 -0
  105. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/schema/schema.py +0 -0
  106. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/servicios/__init__.py +0 -0
  107. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/servicios/thrid/__init__.py +0 -0
  108. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit/servicios/thrid/jwt.py +0 -0
  109. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit.egg-info/dependency_links.txt +0 -0
  110. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/fastapi_basekit.egg-info/top_level.txt +0 -0
  111. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/setup.cfg +0 -0
  112. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_api_exceptions.py +0 -0
  113. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_base_response.py +0 -0
  114. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_base_service.py +0 -0
  115. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_controller_auto_permissions.py +0 -0
  116. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_crud_beanie_controller.py +0 -0
  117. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_crud_controller.py +0 -0
  118. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_jwt_service.py +0 -0
  119. {fastapi_basekit-0.2.1 → fastapi_basekit-0.3.1}/tests/test_sqlalchemy_base_service_order.py +0 -0
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.1
2
+ Name: fastapi-basekit
3
+ Version: 0.3.1
4
+ Summary: Utilities and base classes for FastAPI async projects (Beanie, SQLAlchemy or SQLModel)
5
+ Author-email: Jerson Moreno <jerson.ml820@hotmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/mundobien2025/fastapi-basekit
8
+ Project-URL: Repository, https://github.com/mundobien2025/fastapi-basekit
9
+ Project-URL: Issues, https://github.com/mundobien2025/fastapi-basekit/issues
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: fastapi>=0.116.1
19
+ Requires-Dist: pydantic<3,>=2.13.0
20
+ Requires-Dist: fastapi-restful[all]>=0.6.0
21
+ Requires-Dist: pyjwt>=2.12.1
22
+ Provides-Extra: beanie
23
+ Requires-Dist: beanie<3,>=2.0; extra == "beanie"
24
+ Provides-Extra: sqlalchemy
25
+ Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0.30; extra == "sqlalchemy"
26
+ Requires-Dist: psycopg2>=2.9.0; extra == "sqlalchemy"
27
+ Provides-Extra: sqlmodel
28
+ Requires-Dist: sqlmodel>=0.0.37; extra == "sqlmodel"
29
+ Provides-Extra: init
30
+ Requires-Dist: cookiecutter>=2.5.0; extra == "init"
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocs>=1.6.0; extra == "docs"
33
+ Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
34
+ Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "docs"
35
+ Requires-Dist: pymdown-extensions>=10.10; extra == "docs"
36
+ Requires-Dist: mike>=2.1.0; extra == "docs"
37
+ Provides-Extra: all
38
+ Requires-Dist: beanie<3,>=2.0; extra == "all"
39
+ Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0.30; extra == "all"
40
+ Requires-Dist: psycopg2>=2.9.0; extra == "all"
41
+ Requires-Dist: sqlmodel>=0.0.37; extra == "all"
42
+ Requires-Dist: cookiecutter>=2.5.0; extra == "all"
43
+ Requires-Dist: mkdocs>=1.6.0; extra == "all"
44
+ Requires-Dist: mkdocs-material>=9.5.0; extra == "all"
45
+ Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "all"
46
+
47
+ <div align="center">
48
+
49
+ <img src="docs/assets/logo-mark.svg" width="120" alt="fastapi-basekit">
50
+
51
+ # fastapi-basekit
52
+
53
+ **FastAPI toolkit con repos, services y controllers listos para producción.**
54
+ **Multi-ORM · CLI scaffolder · plugin Claude Code.**
55
+
56
+ [![PyPI](https://img.shields.io/pypi/v/fastapi-basekit?style=flat-square&color=009485&logo=pypi&logoColor=white)](https://pypi.org/project/fastapi-basekit/)
57
+ [![Python](https://img.shields.io/badge/python-3.11+-2c5282?style=flat-square&logo=python&logoColor=white)](https://pypi.org/project/fastapi-basekit/)
58
+ [![FastAPI](https://img.shields.io/badge/FastAPI-005571?style=flat-square&logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com/)
59
+ [![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-2.0-c41616?style=flat-square)](https://www.sqlalchemy.org/)
60
+ [![Beanie](https://img.shields.io/badge/MongoDB-Beanie-47A248?style=flat-square&logo=mongodb&logoColor=white)](https://beanie-odm.dev/)
61
+ [![License](https://img.shields.io/badge/license-MIT-009485?style=flat-square)](./LICENSE)
62
+
63
+ [**Docs**](https://mundobien2025.github.io/fastapi-basekit) ·
64
+ [**PyPI**](https://pypi.org/project/fastapi-basekit/) ·
65
+ [**Issues**](https://github.com/mundobien2025/fastapi-basekit/issues) ·
66
+ [**Changelog**](./CHANGELOG.md)
67
+
68
+ </div>
69
+
70
+ ---
71
+
72
+ ## Quickstart
73
+
74
+ ```bash
75
+ pip install fastapi-basekit[init]
76
+ basekit init # cookiecutter prompts
77
+ cd <project_slug>
78
+ cp .env.example .env
79
+ make up-d
80
+ make migrate-create && make migrate-up
81
+ make seed
82
+ ```
83
+
84
+ Abre http://localhost:8000/docs. Login con `admin@example.com` / `ChangeMe2026!`.
85
+
86
+ ## ¿Qué incluye?
87
+
88
+ - **Repository / Service / Controller** base async para SQLAlchemy 2.0, SQLModel y Beanie
89
+ - **Paginación, filtrado, búsqueda, ordenamiento** vía query string out-of-the-box
90
+ - **JWT middleware** + `BasePermission` classes + soft-delete
91
+ - **`basekit init`** — scaffolder cookiecutter (multi-ORM, multi-DB, redis, s3, license)
92
+ - **Plugin Claude Code** — la skill `fastapi-basekit-crud` enseña el patrón a Claude
93
+
94
+ ## Documentación
95
+
96
+ **[mundobien2025.github.io/fastapi-basekit](https://mundobien2025.github.io/fastapi-basekit)**
97
+
98
+ | Sección | Contenido |
99
+ |---|---|
100
+ | [Primeros pasos](https://mundobien2025.github.io/fastapi-basekit/getting-started/installation) | Instalación, `basekit init`, primer CRUD |
101
+ | [Guía de usuario](https://mundobien2025.github.io/fastapi-basekit/user-guide/controllers) | Controllers, services, repositories, paginación, filtros |
102
+ | [Avanzado](https://mundobien2025.github.io/fastapi-basekit/advanced/permissions) | Permisos, soft-delete, logging, performance |
103
+ | [Referencia API](https://mundobien2025.github.io/fastapi-basekit/api-reference/base-controller) | Docstrings de `BaseController`, `BaseService`, `BaseRepository`, `Schemas` |
104
+ | [Ejemplos](https://mundobien2025.github.io/fastapi-basekit/examples/basic-crud) | CRUD básico, filtros complejos, relaciones, auth |
105
+
106
+ ## Como plugin de Claude Code
107
+
108
+ ```bash
109
+ /plugin marketplace add https://github.com/mundobien2025/fastapi-basekit
110
+ /plugin install fastapi-basekit
111
+ /plugin list
112
+ ```
113
+
114
+ Luego pide: *"Crea el recurso `Invoice` con CRUD completo"* — Claude usa la skill automáticamente.
115
+
116
+ ## Instalación por ORM
117
+
118
+ ```bash
119
+ pip install fastapi-basekit[sqlalchemy] # Postgres / MySQL / SQLite
120
+ pip install fastapi-basekit[beanie] # MongoDB
121
+ pip install fastapi-basekit[sqlmodel]
122
+ pip install fastapi-basekit[init] # solo scaffolder
123
+ pip install fastapi-basekit[all] # todo
124
+ ```
125
+
126
+ ## Contribuir
127
+
128
+ PRs bienvenidos. Setup local en [docs/contributing](https://mundobien2025.github.io/fastapi-basekit/contributing).
129
+
130
+ Mantenedores: [`RELEASING.md`](./RELEASING.md) tiene release flow, CI/CD, mike y troubleshooting.
131
+
132
+ ## Licencia
133
+
134
+ [MIT](./LICENSE) — © Jerson Moreno
@@ -0,0 +1,88 @@
1
+ <div align="center">
2
+
3
+ <img src="docs/assets/logo-mark.svg" width="120" alt="fastapi-basekit">
4
+
5
+ # fastapi-basekit
6
+
7
+ **FastAPI toolkit con repos, services y controllers listos para producción.**
8
+ **Multi-ORM · CLI scaffolder · plugin Claude Code.**
9
+
10
+ [![PyPI](https://img.shields.io/pypi/v/fastapi-basekit?style=flat-square&color=009485&logo=pypi&logoColor=white)](https://pypi.org/project/fastapi-basekit/)
11
+ [![Python](https://img.shields.io/badge/python-3.11+-2c5282?style=flat-square&logo=python&logoColor=white)](https://pypi.org/project/fastapi-basekit/)
12
+ [![FastAPI](https://img.shields.io/badge/FastAPI-005571?style=flat-square&logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com/)
13
+ [![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-2.0-c41616?style=flat-square)](https://www.sqlalchemy.org/)
14
+ [![Beanie](https://img.shields.io/badge/MongoDB-Beanie-47A248?style=flat-square&logo=mongodb&logoColor=white)](https://beanie-odm.dev/)
15
+ [![License](https://img.shields.io/badge/license-MIT-009485?style=flat-square)](./LICENSE)
16
+
17
+ [**Docs**](https://mundobien2025.github.io/fastapi-basekit) ·
18
+ [**PyPI**](https://pypi.org/project/fastapi-basekit/) ·
19
+ [**Issues**](https://github.com/mundobien2025/fastapi-basekit/issues) ·
20
+ [**Changelog**](./CHANGELOG.md)
21
+
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## Quickstart
27
+
28
+ ```bash
29
+ pip install fastapi-basekit[init]
30
+ basekit init # cookiecutter prompts
31
+ cd <project_slug>
32
+ cp .env.example .env
33
+ make up-d
34
+ make migrate-create && make migrate-up
35
+ make seed
36
+ ```
37
+
38
+ Abre http://localhost:8000/docs. Login con `admin@example.com` / `ChangeMe2026!`.
39
+
40
+ ## ¿Qué incluye?
41
+
42
+ - **Repository / Service / Controller** base async para SQLAlchemy 2.0, SQLModel y Beanie
43
+ - **Paginación, filtrado, búsqueda, ordenamiento** vía query string out-of-the-box
44
+ - **JWT middleware** + `BasePermission` classes + soft-delete
45
+ - **`basekit init`** — scaffolder cookiecutter (multi-ORM, multi-DB, redis, s3, license)
46
+ - **Plugin Claude Code** — la skill `fastapi-basekit-crud` enseña el patrón a Claude
47
+
48
+ ## Documentación
49
+
50
+ **[mundobien2025.github.io/fastapi-basekit](https://mundobien2025.github.io/fastapi-basekit)**
51
+
52
+ | Sección | Contenido |
53
+ |---|---|
54
+ | [Primeros pasos](https://mundobien2025.github.io/fastapi-basekit/getting-started/installation) | Instalación, `basekit init`, primer CRUD |
55
+ | [Guía de usuario](https://mundobien2025.github.io/fastapi-basekit/user-guide/controllers) | Controllers, services, repositories, paginación, filtros |
56
+ | [Avanzado](https://mundobien2025.github.io/fastapi-basekit/advanced/permissions) | Permisos, soft-delete, logging, performance |
57
+ | [Referencia API](https://mundobien2025.github.io/fastapi-basekit/api-reference/base-controller) | Docstrings de `BaseController`, `BaseService`, `BaseRepository`, `Schemas` |
58
+ | [Ejemplos](https://mundobien2025.github.io/fastapi-basekit/examples/basic-crud) | CRUD básico, filtros complejos, relaciones, auth |
59
+
60
+ ## Como plugin de Claude Code
61
+
62
+ ```bash
63
+ /plugin marketplace add https://github.com/mundobien2025/fastapi-basekit
64
+ /plugin install fastapi-basekit
65
+ /plugin list
66
+ ```
67
+
68
+ Luego pide: *"Crea el recurso `Invoice` con CRUD completo"* — Claude usa la skill automáticamente.
69
+
70
+ ## Instalación por ORM
71
+
72
+ ```bash
73
+ pip install fastapi-basekit[sqlalchemy] # Postgres / MySQL / SQLite
74
+ pip install fastapi-basekit[beanie] # MongoDB
75
+ pip install fastapi-basekit[sqlmodel]
76
+ pip install fastapi-basekit[init] # solo scaffolder
77
+ pip install fastapi-basekit[all] # todo
78
+ ```
79
+
80
+ ## Contribuir
81
+
82
+ PRs bienvenidos. Setup local en [docs/contributing](https://mundobien2025.github.io/fastapi-basekit/contributing).
83
+
84
+ Mantenedores: [`RELEASING.md`](./RELEASING.md) tiene release flow, CI/CD, mike y troubleshooting.
85
+
86
+ ## Licencia
87
+
88
+ [MIT](./LICENSE) — © Jerson Moreno
@@ -2,9 +2,9 @@ from typing import Any, Dict, List, Optional, Type, Union
2
2
  from typing import get_args, get_origin
3
3
  import re
4
4
 
5
- from bson import ObjectId, Link
5
+ from bson import ObjectId
6
6
  from pydantic import BaseModel
7
- from beanie import Document
7
+ from beanie import Document, Link
8
8
  from beanie.odm.queries.find import FindMany
9
9
  from beanie.operators import Or, RegEx
10
10
 
@@ -154,18 +154,42 @@ class BaseRepository:
154
154
 
155
155
  raw_filters: Dict[str, Any] = {}
156
156
 
157
+ def _coerce_objectid(value: Any) -> Any:
158
+ """str/UUID → ObjectId para queries Mongo sobre Link.id."""
159
+ if isinstance(value, ObjectId):
160
+ return value
161
+ if isinstance(value, str):
162
+ try:
163
+ return ObjectId(value)
164
+ except Exception:
165
+ return value
166
+ return value
167
+
157
168
  for k, v in (filters or {}).items():
158
169
  # MongoDB-style keys (dot-notation like "user.$id" or operators like "$or")
159
170
  # cannot be resolved via hasattr — pass them as a raw dict to find()
160
171
  if "." in k or k.startswith("$"):
161
172
  raw_filters[k] = v
162
- elif hasattr(self.model, k):
163
- field_attr = getattr(self.model, k)
173
+ continue
164
174
 
175
+ if hasattr(self.model, k):
176
+ field_attr = getattr(self.model, k)
165
177
  if _is_link_field(k):
166
- exprs.append(field_attr.id == v)
178
+ exprs.append(field_attr.id == _coerce_objectid(v))
167
179
  else:
168
180
  exprs.append(field_attr == v)
181
+ continue
182
+
183
+ # `<field>_id` alias para Link fields. Permite filtros con
184
+ # `customer_id`, `user_id`, etc. sin que el caller deba conocer
185
+ # la sintaxis Mongo nested. Sólo se activa si <field> existe en
186
+ # el modelo y es un Link[X].
187
+ if k.endswith("_id"):
188
+ base = k[:-3]
189
+ if hasattr(self.model, base) and _is_link_field(base):
190
+ field_attr = getattr(self.model, base)
191
+ exprs.append(field_attr.id == _coerce_objectid(v))
192
+ continue
169
193
 
170
194
  # Raw MongoDB filters go first so Beanie processes them as a dict condition
171
195
  query_args: list = ([raw_filters] if raw_filters else []) + exprs
@@ -1,6 +1,7 @@
1
- __all__ = ["repository", "service", "controller"]
2
1
  from .controller.base import SQLAlchemyBaseController
2
+ from .session import make_session_lifecycle
3
3
 
4
4
  __all__ = [
5
5
  "SQLAlchemyBaseController",
6
+ "make_session_lifecycle",
6
7
  ]
@@ -10,7 +10,14 @@ from ....exceptions.api_exceptions import (
10
10
 
11
11
 
12
12
  class BaseService:
13
- """Servicio base para SQLAlchemy AsyncSession."""
13
+ """Servicio base para SQLAlchemy AsyncSession.
14
+
15
+ Regla del proyecto: los servicios NO deben llamar `session.flush()`,
16
+ `session.commit()` ni `session.refresh()`. El flush vive en
17
+ `BaseRepository.create / update`; el commit/rollback único por
18
+ request lo gestiona el lifecycle creado con
19
+ `fastapi_basekit.aio.sqlalchemy.make_session_lifecycle`.
20
+ """
14
21
 
15
22
  repository: BaseRepository
16
23
  search_fields: List[str] = []
@@ -0,0 +1,67 @@
1
+ """Reusable async SQLAlchemy session lifecycle.
2
+
3
+ Single commit / single rollback per request. Optional hooks fire after
4
+ commit (success) or rollback (error) so callers can attach logging,
5
+ metrics, sentry, retries, etc. without coupling the lifecycle to any
6
+ specific telemetry stack.
7
+
8
+ Usage:
9
+
10
+ from fastapi_basekit.aio.sqlalchemy.session import make_session_lifecycle
11
+ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
12
+
13
+ engine = create_async_engine(DATABASE_URL)
14
+ SessionFactory = async_sessionmaker(engine, autoflush=False, expire_on_commit=False)
15
+
16
+ async def on_error(exc, session):
17
+ logger.exception("rollback", exc_info=exc)
18
+
19
+ get_db = make_session_lifecycle(SessionFactory, on_error=on_error)
20
+
21
+ # then in routes: ... session: AsyncSession = Depends(get_db)
22
+
23
+ Rule of thumb for callers:
24
+ - Services SHOULD NOT call session.flush / session.commit / session.refresh.
25
+ - Repositories own flush via BaseRepository.create / update.
26
+ - get_db owns the single commit (success) and rollback (error).
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from collections.abc import AsyncGenerator, Awaitable, Callable
32
+ from typing import Optional
33
+
34
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
35
+
36
+ ErrorHook = Callable[[Exception, AsyncSession], Awaitable[None]]
37
+ SuccessHook = Callable[[AsyncSession], Awaitable[None]]
38
+
39
+
40
+ def make_session_lifecycle(
41
+ session_factory: async_sessionmaker[AsyncSession],
42
+ *,
43
+ on_success: Optional[SuccessHook] = None,
44
+ on_error: Optional[ErrorHook] = None,
45
+ ) -> Callable[[], AsyncGenerator[AsyncSession, None]]:
46
+ """Return an async generator dependency that owns the request session.
47
+
48
+ The returned callable yields a session, commits on success, rolls back
49
+ on error, and invokes optional hooks. Hooks must not raise — wrap them
50
+ in try/except at the call site if their failure should not poison the
51
+ request.
52
+ """
53
+
54
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
55
+ async with session_factory() as session:
56
+ try:
57
+ yield session
58
+ await session.commit()
59
+ if on_success is not None:
60
+ await on_success(session)
61
+ except Exception as exc:
62
+ await session.rollback()
63
+ if on_error is not None:
64
+ await on_error(exc, session)
65
+ raise
66
+
67
+ return get_db
@@ -0,0 +1,5 @@
1
+ """basekit CLI — entry point for `basekit init`."""
2
+
3
+ from fastapi_basekit.cli.main import main
4
+
5
+ __all__ = ["main"]
@@ -0,0 +1,112 @@
1
+ """basekit CLI entry point.
2
+
3
+ Usage:
4
+ basekit init # interactive cookiecutter prompts
5
+ basekit init --no-input # use defaults (good for CI / testing)
6
+ basekit init -o /path # output dir
7
+ basekit init --extra-context key=value
8
+ basekit version
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import sys
15
+ from importlib import metadata
16
+ from pathlib import Path
17
+ from typing import Sequence
18
+
19
+
20
+ def _template_dir() -> Path:
21
+ return Path(__file__).resolve().parent.parent / "templates" / "project"
22
+
23
+
24
+ def _cmd_init(args: argparse.Namespace) -> int:
25
+ try:
26
+ from cookiecutter.main import cookiecutter
27
+ except ImportError:
28
+ print(
29
+ "Error: cookiecutter not installed. Install with:\n"
30
+ " pip install fastapi-basekit[init]\n"
31
+ "or pip install cookiecutter",
32
+ file=sys.stderr,
33
+ )
34
+ return 1
35
+
36
+ extra_context: dict[str, str] = {}
37
+ for kv in args.extra_context or []:
38
+ if "=" not in kv:
39
+ print(f"Bad --extra-context value (expected key=value): {kv}", file=sys.stderr)
40
+ return 2
41
+ k, v = kv.split("=", 1)
42
+ extra_context[k.strip()] = v.strip()
43
+
44
+ try:
45
+ cookiecutter(
46
+ template=str(_template_dir()),
47
+ no_input=args.no_input,
48
+ output_dir=args.output_dir,
49
+ extra_context=extra_context,
50
+ overwrite_if_exists=args.overwrite,
51
+ )
52
+ except Exception as exc: # pragma: no cover - cookiecutter raises various
53
+ print(f"basekit init failed: {exc}", file=sys.stderr)
54
+ return 3
55
+ return 0
56
+
57
+
58
+ def _cmd_version(_: argparse.Namespace) -> int:
59
+ try:
60
+ v = metadata.version("fastapi-basekit")
61
+ except metadata.PackageNotFoundError: # pragma: no cover
62
+ v = "unknown"
63
+ print(f"fastapi-basekit {v}")
64
+ return 0
65
+
66
+
67
+ def build_parser() -> argparse.ArgumentParser:
68
+ parser = argparse.ArgumentParser(
69
+ prog="basekit",
70
+ description="fastapi-basekit project scaffolder.",
71
+ )
72
+ sub = parser.add_subparsers(dest="cmd", required=True)
73
+
74
+ init = sub.add_parser("init", help="Scaffold a new project from the basekit template.")
75
+ init.add_argument(
76
+ "-o",
77
+ "--output-dir",
78
+ default=".",
79
+ help="Where to write the new project (default: current directory).",
80
+ )
81
+ init.add_argument(
82
+ "--no-input",
83
+ action="store_true",
84
+ help="Skip interactive prompts; use cookiecutter.json defaults.",
85
+ )
86
+ init.add_argument(
87
+ "--overwrite",
88
+ action="store_true",
89
+ help="Overwrite the output directory if it already exists.",
90
+ )
91
+ init.add_argument(
92
+ "--extra-context",
93
+ action="append",
94
+ metavar="KEY=VALUE",
95
+ help="Override one cookiecutter variable; repeatable.",
96
+ )
97
+ init.set_defaults(func=_cmd_init)
98
+
99
+ version = sub.add_parser("version", help="Print the installed version.")
100
+ version.set_defaults(func=_cmd_version)
101
+
102
+ return parser
103
+
104
+
105
+ def main(argv: Sequence[str] | None = None) -> int:
106
+ parser = build_parser()
107
+ args = parser.parse_args(argv)
108
+ return args.func(args)
109
+
110
+
111
+ if __name__ == "__main__": # pragma: no cover
112
+ raise SystemExit(main())
@@ -0,0 +1,19 @@
1
+ {
2
+ "project_name": "My FastAPI Service",
3
+ "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}",
4
+ "package_name": "{{ cookiecutter.project_slug }}",
5
+ "description": "Backend API built with fastapi-basekit",
6
+ "author_name": "Your Name",
7
+ "author_email": "you@example.com",
8
+ "python_version": "3.12",
9
+ "orm": ["sqlalchemy", "beanie"],
10
+ "database": ["postgres", "mariadb", "sqlite", "mongodb"],
11
+ "server": ["uvicorn", "gunicorn"],
12
+ "cache": ["none", "redis"],
13
+ "background_tasks": ["none", "arq"],
14
+ "bucket": ["none", "s3"],
15
+ "include_alembic": ["yes", "no"],
16
+ "license": ["MIT", "Apache-2.0", "GPL-3.0", "Proprietary"],
17
+ "include_docker": ["yes", "no"],
18
+ "_extensions": ["jinja2.ext.do"]
19
+ }
@@ -0,0 +1,51 @@
1
+ """Cleanup files that aren't needed for the chosen options."""
2
+
3
+ import os
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ ORM = "{{ cookiecutter.orm }}"
8
+ DB = "{{ cookiecutter.database }}"
9
+ INCLUDE_ALEMBIC = "{{ cookiecutter.include_alembic }}" == "yes"
10
+ INCLUDE_DOCKER = "{{ cookiecutter.include_docker }}" == "yes"
11
+ CACHE = "{{ cookiecutter.cache }}"
12
+ BUCKET = "{{ cookiecutter.bucket }}"
13
+
14
+ PROJECT_DIR = Path(os.getcwd())
15
+
16
+
17
+ def rm(path: str) -> None:
18
+ p = PROJECT_DIR / path
19
+ if p.is_dir():
20
+ shutil.rmtree(p, ignore_errors=True)
21
+ elif p.exists():
22
+ p.unlink()
23
+
24
+
25
+ # 1. Beanie projects don't use alembic
26
+ if ORM == "beanie" or not INCLUDE_ALEMBIC:
27
+ rm("alembic")
28
+ rm("alembic.ini")
29
+
30
+ # 2. Docker is opt-out
31
+ if not INCLUDE_DOCKER:
32
+ rm("docker-compose.yml")
33
+ rm("Dockerfile")
34
+
35
+ # 3. Print next steps
36
+ print(
37
+ f"\n✓ Project scaffolded at {PROJECT_DIR}\n"
38
+ f" ORM : {ORM}\n"
39
+ f" Database : {DB}\n"
40
+ f" Cache : {CACHE}\n"
41
+ f" Bucket : {BUCKET}\n"
42
+ f" Alembic : {'yes' if INCLUDE_ALEMBIC and ORM != 'beanie' else 'no'}\n"
43
+ f" Docker : {'yes' if INCLUDE_DOCKER else 'no'}\n"
44
+ f"\nNext steps:\n"
45
+ f" cd {PROJECT_DIR.name}\n"
46
+ f" cp .env.example .env # edit values\n"
47
+ + (" make up # start containers\n" if INCLUDE_DOCKER else " pip install -r requirements.txt\n uvicorn app.main:app --reload\n")
48
+ + (f" make migrate-create # generate baseline alembic\n make migrate-up\n" if ORM != 'beanie' and INCLUDE_ALEMBIC else "")
49
+ + f" make seed # seed admin user\n"
50
+ f"\nVisit http://localhost:8000/docs\n"
51
+ )
@@ -0,0 +1,32 @@
1
+ """Validate cookiecutter inputs before scaffolding."""
2
+
3
+ import re
4
+ import sys
5
+
6
+ slug = "{{ cookiecutter.project_slug }}"
7
+ orm = "{{ cookiecutter.orm }}"
8
+ db = "{{ cookiecutter.database }}"
9
+
10
+ # 1. Slug must be a valid Python identifier
11
+ if not re.match(r"^[a-z][a-z0-9_]*$", slug):
12
+ print(
13
+ f"ERROR: project_slug '{slug}' is invalid. "
14
+ "Must start with a lowercase letter and contain only [a-z0-9_].",
15
+ file=sys.stderr,
16
+ )
17
+ sys.exit(1)
18
+
19
+ # 2. ORM ↔ database compatibility
20
+ if orm == "beanie" and db != "mongodb":
21
+ print(
22
+ f"ERROR: orm=beanie requires database=mongodb (got '{db}').",
23
+ file=sys.stderr,
24
+ )
25
+ sys.exit(1)
26
+
27
+ if orm in ("sqlalchemy", "sqlmodel") and db == "mongodb":
28
+ print(
29
+ f"ERROR: orm={orm} requires a SQL database (postgres/mariadb/sqlite), got '{db}'.",
30
+ file=sys.stderr,
31
+ )
32
+ sys.exit(1)
@@ -0,0 +1,40 @@
1
+ PROJECT_NAME={{ cookiecutter.project_name }}
2
+ VERSION=0.1.0
3
+ DEBUG=true
4
+ ENVIRONMENT=local
5
+
6
+ SECRET_KEY=change-me-in-production
7
+ JWT_SECRET=change-me-in-production
8
+ JWT_ALGORITHM=HS256
9
+ JWT_EXPIRE_SECONDS=3600
10
+ ACCESS_TOKEN_EXPIRE_MINUTES=60
11
+ REFRESH_TOKEN_EXPIRE_DAYS=7
12
+
13
+ {% if cookiecutter.orm == "beanie" -%}
14
+ DATABASE_URL=mongodb://root:secret@db:27017
15
+ DATABASE_NAME={{ cookiecutter.package_name }}
16
+ {%- elif cookiecutter.database == "postgres" -%}
17
+ DATABASE_URL=postgresql+asyncpg://{{ cookiecutter.package_name }}:secret@db:5432/{{ cookiecutter.package_name }}
18
+ {%- elif cookiecutter.database == "mariadb" -%}
19
+ DATABASE_URL=mysql+aiomysql://{{ cookiecutter.package_name }}:secret@db:3306/{{ cookiecutter.package_name }}
20
+ {%- elif cookiecutter.database == "sqlite" -%}
21
+ DATABASE_URL=sqlite+aiosqlite:///./{{ cookiecutter.package_name }}.db
22
+ {%- endif %}
23
+
24
+ {% if cookiecutter.cache == "redis" -%}
25
+ REDIS_HOST=redis
26
+ REDIS_PORT=6379
27
+ REDIS_DB=0
28
+ REDIS_PASSWORD=
29
+ {%- endif %}
30
+
31
+ {% if cookiecutter.bucket == "s3" -%}
32
+ AWS_ACCESS_KEY_ID=
33
+ AWS_SECRET_ACCESS_KEY=
34
+ AWS_REGION_NAME=us-east-1
35
+ AWS_S3_BUCKET_NAME=
36
+ {%- endif %}
37
+
38
+ ADMIN_EMAIL=admin@example.com
39
+ ADMIN_PASSWORD=ChangeMe2026!
40
+ ADMIN_NAME=Platform Admin
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ .env
7
+ .coverage
8
+ htmlcov/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ *.sqlite3
12
+ uploads/
@@ -0,0 +1,18 @@
1
+ FROM python:{{ cookiecutter.python_version }}-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ build-essential \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ COPY . .
13
+
14
+ {% if cookiecutter.server == "gunicorn" -%}
15
+ CMD ["gunicorn", "app.main:app", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "--workers", "4"]
16
+ {%- else -%}
17
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
18
+ {%- endif %}