fabrik-fastapi 1.0.0__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 (59) hide show
  1. fabrik_fastapi-1.0.0/ARCHITECTURE.md +414 -0
  2. fabrik_fastapi-1.0.0/LICENSE +21 -0
  3. fabrik_fastapi-1.0.0/MANIFEST.in +26 -0
  4. fabrik_fastapi-1.0.0/PKG-INFO +220 -0
  5. fabrik_fastapi-1.0.0/README.md +187 -0
  6. fabrik_fastapi-1.0.0/docs/PUBLISHING.md +192 -0
  7. fabrik_fastapi-1.0.0/docs/USAGE.md +619 -0
  8. fabrik_fastapi-1.0.0/fabrik/__init__.py +18 -0
  9. fabrik_fastapi-1.0.0/fabrik/__main__.py +8 -0
  10. fabrik_fastapi-1.0.0/fabrik/core/.dockerignore +7 -0
  11. fabrik_fastapi-1.0.0/fabrik/core/.env.example +9 -0
  12. fabrik_fastapi-1.0.0/fabrik/core/.gitignore +12 -0
  13. fabrik_fastapi-1.0.0/fabrik/core/_templates/Dockerfile.tpl +7 -0
  14. fabrik_fastapi-1.0.0/fabrik/core/_templates/env.tpl +8 -0
  15. fabrik_fastapi-1.0.0/fabrik/core/_templates/main.py.tpl +86 -0
  16. fabrik_fastapi-1.0.0/fabrik/core/alembic/README +1 -0
  17. fabrik_fastapi-1.0.0/fabrik/core/alembic/env.py +42 -0
  18. fabrik_fastapi-1.0.0/fabrik/core/alembic/script.py.mako +23 -0
  19. fabrik_fastapi-1.0.0/fabrik/core/alembic/versions/.gitkeep +0 -0
  20. fabrik_fastapi-1.0.0/fabrik/core/alembic.ini +39 -0
  21. fabrik_fastapi-1.0.0/fabrik/core/create_superuser.py +56 -0
  22. fabrik_fastapi-1.0.0/fabrik/core/docker-compose.yml +34 -0
  23. fabrik_fastapi-1.0.0/fabrik/core/pytest.ini +3 -0
  24. fabrik_fastapi-1.0.0/fabrik/core/requirements.txt +38 -0
  25. fabrik_fastapi-1.0.0/fabrik/core/src/__init__.py +0 -0
  26. fabrik_fastapi-1.0.0/fabrik/core/src/admin/__init__.py +0 -0
  27. fabrik_fastapi-1.0.0/fabrik/core/src/admin/router.py +563 -0
  28. fabrik_fastapi-1.0.0/fabrik/core/src/admin/static/admin.css +1086 -0
  29. fabrik_fastapi-1.0.0/fabrik/core/src/admin/templates/base.html +115 -0
  30. fabrik_fastapi-1.0.0/fabrik/core/src/admin/templates/dashboard.html +158 -0
  31. fabrik_fastapi-1.0.0/fabrik/core/src/admin/templates/form.html +71 -0
  32. fabrik_fastapi-1.0.0/fabrik/core/src/admin/templates/list.html +143 -0
  33. fabrik_fastapi-1.0.0/fabrik/core/src/admin/templates/login.html +36 -0
  34. fabrik_fastapi-1.0.0/fabrik/core/src/core/__init__.py +0 -0
  35. fabrik_fastapi-1.0.0/fabrik/core/src/core/config.py +35 -0
  36. fabrik_fastapi-1.0.0/fabrik/core/src/core/mixins.py +13 -0
  37. fabrik_fastapi-1.0.0/fabrik/core/src/core/pagination.py +32 -0
  38. fabrik_fastapi-1.0.0/fabrik/core/src/core/security.py +70 -0
  39. fabrik_fastapi-1.0.0/fabrik/core/src/database.py +25 -0
  40. fabrik_fastapi-1.0.0/fabrik/core/src/tasks.py +79 -0
  41. fabrik_fastapi-1.0.0/fabrik/core/src/users/__init__.py +0 -0
  42. fabrik_fastapi-1.0.0/fabrik/core/src/users/models.py +22 -0
  43. fabrik_fastapi-1.0.0/fabrik/core/src/users/router.py +74 -0
  44. fabrik_fastapi-1.0.0/fabrik/core/src/users/schemas.py +36 -0
  45. fabrik_fastapi-1.0.0/fabrik/core/src/users/service.py +56 -0
  46. fabrik_fastapi-1.0.0/fabrik/core/tests/__init__.py +0 -0
  47. fabrik_fastapi-1.0.0/fabrik/core/tests/conftest.py +54 -0
  48. fabrik_fastapi-1.0.0/fabrik/core/tests/test_tasks.py +30 -0
  49. fabrik_fastapi-1.0.0/fabrik/core/tests/test_users.py +55 -0
  50. fabrik_fastapi-1.0.0/fabrik/core/worker.py +18 -0
  51. fabrik_fastapi-1.0.0/fabrik/scaffold.py +1173 -0
  52. fabrik_fastapi-1.0.0/fabrik_fastapi.egg-info/PKG-INFO +220 -0
  53. fabrik_fastapi-1.0.0/fabrik_fastapi.egg-info/SOURCES.txt +57 -0
  54. fabrik_fastapi-1.0.0/fabrik_fastapi.egg-info/dependency_links.txt +1 -0
  55. fabrik_fastapi-1.0.0/fabrik_fastapi.egg-info/entry_points.txt +2 -0
  56. fabrik_fastapi-1.0.0/fabrik_fastapi.egg-info/requires.txt +4 -0
  57. fabrik_fastapi-1.0.0/fabrik_fastapi.egg-info/top_level.txt +1 -0
  58. fabrik_fastapi-1.0.0/pyproject.toml +67 -0
  59. fabrik_fastapi-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,414 @@
1
+ # Architecture de Fabrik
2
+
3
+ > Document de reference sur les decisions de conception, le decoupage du code,
4
+ > et le cycle de vie d'un projet genere.
5
+
6
+ **Auteur :** Falandy Jean
7
+ **Version :** 1.0.0
8
+
9
+ ---
10
+
11
+ ## 1. Philosophie
12
+
13
+ Fabrik est un **opinionated framework** construit au-dessus de FastAPI. Il
14
+ prend des decisions pour toi sur :
15
+
16
+ - L'asynchronisme (tout est `async`, pas de blocage thread sur les I/O DB)
17
+ - La structure de fichiers (`src/<module>/` avec separation
18
+ `models / schemas / service / router`)
19
+ - La securite par defaut (CORS strict, SECRET_KEY 256 bits, bcrypt, JWT)
20
+ - Les outils de developpement (tests isoles, migrations, admin UI)
21
+
22
+ **Ce que Fabrik refuse de faire :**
23
+ - Devenir un framework "universel" (pas de plugins, pas d'ecosysteme tiers)
24
+ - Cacher SQLAlchemy ou FastAPI (tu vois et controle tout)
25
+ - Etre retro-compatible avec les vieux Python (Python 3.13+ uniquement)
26
+
27
+ ---
28
+
29
+ ## 2. Decoupage du repo
30
+
31
+ ```
32
+ fabrik/ ← repo GitHub
33
+ ├── pyproject.toml Metadata PyPI (nom, version, deps, entry_points)
34
+ ├── MANIFEST.in Inclusion de core/ dans la sdist
35
+ ├── README.md Vitrine + installation
36
+ ├── ARCHITECTURE.md ← ce fichier
37
+ ├── LICENSE MIT
38
+ ├── docs/
39
+ │ ├── USAGE.md Guide utilisateur complet
40
+ │ └── PUBLISHING.md Workflow de release PyPI
41
+ ├── .github/workflows/ci.yml CI (lance test-self a chaque commit)
42
+ └── fabrik/ ← PACKAGE Python publie sur PyPI
43
+ ├── __init__.py Version + exports
44
+ ├── __main__.py Pour `python -m fabrik`
45
+ ├── scaffold.py Moteur CLI
46
+ └── core/ Templates copies dans chaque projet
47
+ ```
48
+
49
+ Quand l'utilisateur fait `pip install fabrik-cli`, c'est le dossier
50
+ **inner `fabrik/`** qui est installe dans le `site-packages` de son Python.
51
+ La commande `fabrik` est creee par l'entry point `[project.scripts]` de
52
+ `pyproject.toml` qui pointe vers `fabrik.scaffold:main`.
53
+
54
+ ### 2.1 Pourquoi `core/` est un dossier separe ?
55
+
56
+ A la version 0 du prototype, **tous les templates etaient des chaines Python
57
+ embarquees dans `scaffold.py`** (3902 lignes). Probleme :
58
+
59
+ - Pas de coloration syntaxique dans l'IDE pour les templates Jinja2 / HTML / CSS
60
+ - Difficile de chercher et editer un fichier specifique
61
+ - Mauvais ratio signal/bruit a la lecture de `scaffold.py`
62
+
63
+ Depuis v1.0, **les templates vivent comme de vrais fichiers dans `core/`** :
64
+
65
+ - `core/<chemin>` -> fichier statique copie tel quel
66
+ - `core/_templates/*.tpl` -> fichier avec substitution `string.Template`
67
+ (`${title}`, `${port}`, `${secret_key}`, `${db_url}`)
68
+
69
+ `build_files()` est passe de 2750 lignes hardcodees a 20 lignes qui font un
70
+ `rglob` sur `core/`.
71
+
72
+ ---
73
+
74
+ ## 3. Cycle de vie d'un projet
75
+
76
+ ### 3.1 `scaffold.py new mon-api`
77
+
78
+ ```
79
+ cmd_new()
80
+
81
+ ├── build_files(title, port, db_url)
82
+ │ └── walk core/ -> dict {chemin: contenu}
83
+ │ └── string.Template.substitute() pour core/_templates/*.tpl
84
+
85
+ ├── Pour chaque (chemin, contenu) : f.write_text(contenu)
86
+
87
+ ├── Ecrit .scaffold-version (JSON : version + date + patches_applied)
88
+
89
+ ├── subprocess: python -m venv venv
90
+ ├── subprocess: venv/pip install -r requirements.txt
91
+ └── subprocess: venv/python -m alembic revision/upgrade
92
+ ```
93
+
94
+ ### 3.2 `scaffold.py add videos` (depuis la racine du projet)
95
+
96
+ ```
97
+ cmd_add()
98
+
99
+ ├── Genere les 5 fichiers du module
100
+ │ (models.py, schemas.py, service.py, router.py, test_videos.py)
101
+ │ via les Templates string.Template MODULE_MODELS, MODULE_SCHEMAS, ...
102
+
103
+ ├── Auto-wiring (idempotent) :
104
+ │ ├── main.py : ajoute import + include_router
105
+ │ ├── alembic/env.py : ajoute import src.videos.models
106
+ │ └── src/users/models.py : ajoute relation back_populates
107
+
108
+ ├── subprocess: alembic revision --autogenerate -m add_videos
109
+ ├── subprocess: alembic upgrade head
110
+ └── subprocess: pytest tests/test_videos.py
111
+ ```
112
+
113
+ L'auto-wiring utilise `_insert_after(content, anchor, new_line)` qui :
114
+ 1. Verifie que la ligne (strip) n'est pas deja presente dans le fichier
115
+ 2. Trouve la premiere occurrence de l'anchor
116
+ 3. Insere la nouvelle ligne juste apres
117
+
118
+ Cette idempotence permet de relancer `add` sans dupliquer les imports.
119
+
120
+ ### 3.3 `scaffold.py upgrade` (depuis la racine du projet)
121
+
122
+ ```
123
+ cmd_upgrade()
124
+
125
+ ├── Lit .scaffold-version -> version courante du projet
126
+ ├── Compare avec SCAFFOLD_VERSION (constante en haut de scaffold.py)
127
+ ├── Si projet < scaffold :
128
+ │ └── Pour chaque patch dans PATCHES dont [from, to] est compris :
129
+ │ └── patch["apply"](root) ← fonction idempotente
130
+ │ └── Met a jour .scaffold-version
131
+
132
+ └── Si projet >= scaffold : "deja a jour"
133
+ ```
134
+
135
+ Les fonctions de patch :
136
+ - Recoivent `root: Path` (racine du projet a patcher)
137
+ - Doivent etre **idempotentes** : safe a relancer N fois
138
+ - Doivent ecrire un backup `.bak` avant tout ecrasement destructif
139
+ - Retournent un `dict {chemin: statut}` pour le rapport
140
+
141
+ ---
142
+
143
+ ## 4. Decisions de conception : le projet genere
144
+
145
+ ### 4.1 Pourquoi `src/<module>/` au lieu d'un layout plat ?
146
+
147
+ Le decoupage en `models / schemas / service / router` impose **la separation
148
+ des responsabilites** :
149
+
150
+ - `models.py` : ORM SQLAlchemy (donnees + relations)
151
+ - `schemas.py` : validation Pydantic (input/output API)
152
+ - `service.py` : logique metier (operations sur la DB)
153
+ - `router.py` : routes HTTP (mapping URL -> service)
154
+
155
+ Effet de bord : chaque module est **extractible en microservice** plus tard
156
+ sans refactor douloureux.
157
+
158
+ ### 4.2 Pourquoi tout en async ?
159
+
160
+ FastAPI + Starlette + uvicorn sont conçus pour async. Une route sync bloque
161
+ le worker pendant l'attente DB. Avec `AsyncSession` + `asyncpg`, un seul
162
+ process Python peut servir des milliers de requetes/seconde.
163
+
164
+ Cout : la syntaxe `await` partout, et `db.execute(select(...))` au lieu de
165
+ `db.query(...).filter(...).all()`. Mais c'est le standard SQLAlchemy 2.0.
166
+
167
+ ### 4.3 Pourquoi un `lifespan` ?
168
+
169
+ ```python
170
+ @asynccontextmanager
171
+ async def lifespan(app: FastAPI):
172
+ async with engine.begin() as conn:
173
+ await conn.run_sync(Base.metadata.create_all)
174
+ yield
175
+ await engine.dispose()
176
+ ```
177
+
178
+ Le lifespan remplace l'ancien `@app.on_event("startup")` deprecie. Il :
179
+ - Cree les tables si elles n'existent pas (utile en dev sans Alembic)
180
+ - Garantit le `dispose()` du pool de connexions a l'arret (pas de leak)
181
+ - Est `async` natif (vs les hooks synchrones de l'ancienne API)
182
+
183
+ ### 4.4 Pourquoi SECRET_KEY est generee a chaque `new` ?
184
+
185
+ `secrets.token_urlsafe(32)` = 256 bits d'entropie. Chaque projet a son propre
186
+ secret des le depart, jamais commit accidentellement (le `.env` est dans
187
+ `.gitignore`). PyJWT exige 32+ bytes pour HMAC-SHA256.
188
+
189
+ ### 4.5 Pourquoi CORS strict par defaut ?
190
+
191
+ ```python
192
+ BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:5173
193
+ ```
194
+
195
+ Avec `allow_origins=["*"]` + `allow_credentials=True`, Starlette desactive
196
+ silencieusement les cookies (specification CORS). En forcant une liste
197
+ explicite, on evite cette piege ET on empeche un site malveillant de
198
+ proxifier l'API au nom de l'utilisateur.
199
+
200
+ ### 4.6 Pourquoi tests isoles via fixture `client` ?
201
+
202
+ ```python
203
+ # tests/conftest.py
204
+ @pytest_asyncio.fixture
205
+ async def client(test_engine):
206
+ async def override_get_db():
207
+ async with TestSession() as session:
208
+ yield session
209
+ app.dependency_overrides[get_db] = override_get_db
210
+ async with AsyncClient(transport=ASGITransport(app=app), ...) as c:
211
+ yield c
212
+ app.dependency_overrides.clear()
213
+ ```
214
+
215
+ Chaque test recoit une **SQLite in-memory toute fraiche** (avec `StaticPool`
216
+ pour partager la meme connexion entre fixture et requete). La vraie `app.db`
217
+ de dev n'est jamais touchee par pytest.
218
+
219
+ ---
220
+
221
+ ## 5. Decisions de conception : l'admin UI
222
+
223
+ ### 5.1 Auto-discovery via `Base.registry.mappers`
224
+
225
+ Plutot que de demander aux utilisateurs de declarer leurs modeles dans
226
+ l'admin (a la Django `admin.site.register()`), Fabrik **scanne dynamiquement
227
+ SQLAlchemy** :
228
+
229
+ ```python
230
+ def get_admin_models() -> dict:
231
+ return {m.class_.__tablename__: m.class_ for m in Base.registry.mappers}
232
+ ```
233
+
234
+ Resultat : des qu'un module ajoute une classe heritant de `Base`, elle
235
+ apparait dans la sidebar. Zero configuration.
236
+
237
+ ### 5.2 Formulaires generes par introspection des colonnes
238
+
239
+ `col_input_type(col)` mappe les types SQLAlchemy vers des `<input type="...">` :
240
+
241
+ | Type SQLAlchemy | input HTML |
242
+ |------------------------|-------------------|
243
+ | `Boolean` | `checkbox` |
244
+ | `Integer`, `Numeric` | `number` |
245
+ | `DateTime` | `datetime-local` |
246
+ | `Date` | `date` |
247
+ | nom contient "email" | `email` |
248
+ | nom == "password" | `password` |
249
+ | presence de FK | `select` (dropdown avec resolution display field) |
250
+ | autre | `text` |
251
+
252
+ ### 5.3 FK dropdowns intelligents
253
+
254
+ Quand une colonne est une FK, l'admin :
255
+ 1. Detecte la table cible via `col.foreign_keys`
256
+ 2. Charge jusqu'a 500 lignes de la table cible
257
+ 3. Choisit la meilleure colonne d'affichage : `email` > `name` > `title` >
258
+ `label` > `username` > `id`
259
+ 4. Rend un `<select>` avec `<option value="{uuid}">email@example.com ({uuid_court})</option>`
260
+
261
+ ### 5.4 Multi-column search (v1)
262
+
263
+ Recherche sans configuration : `?q=foo` -> ILIKE `%foo%` sur **toutes** les
264
+ colonnes `varchar`/`string`/`text` (sauf `password`), combinees en `or_(...)`.
265
+
266
+ ### 5.5 Bulk delete (v1)
267
+
268
+ Checkboxes par ligne + action bar sticky. La route `POST /admin/{table}/bulk-delete`
269
+ recoit `ids[]` et execute `delete().where(Model.id.in_(ids))` -- **une seule
270
+ requete SQL** pour N suppressions.
271
+
272
+ ### 5.6 CSV export (v1)
273
+
274
+ `GET /admin/{table}/export.csv` -> `StreamingResponse` avec `csv.writer`.
275
+ Nom de fichier : `{table}-{YYYY-MM-DD}.csv`. Toutes les colonnes sauf
276
+ `password`.
277
+
278
+ ### 5.7 Responsive design
279
+
280
+ Le CSS utilise un `@media (max-width: 768px)` qui transforme la sidebar en
281
+ **drawer slide-in** avec hamburger + backdrop. Les `input` mobile sont en
282
+ `font-size: 16px` pour empecher le zoom iOS au focus.
283
+
284
+ ---
285
+
286
+ ## 6. Background tasks : pourquoi ARQ
287
+
288
+ ### 6.1 Le besoin
289
+
290
+ Toute application un peu serieuse a besoin de **deleguer des operations
291
+ lentes** hors du cycle requete/reponse HTTP :
292
+ - Envoi d'emails / notifications push
293
+ - Ingestion / parsing de fichiers volumineux (PDFs, CSVs)
294
+ - Calculs longs (rapports, exports, machine learning)
295
+ - Appels d'API externes lents ou peu fiables (retry)
296
+
297
+ Faire ces operations dans la route bloque le worker et timeout cote client.
298
+
299
+ ### 6.2 Pourquoi pas un worker Go ?
300
+
301
+ Tentation classique : "Python est lent, mettons un worker en Go pour la
302
+ performance." En realite, **95% des taches typiques sont I/O-bound** (attente
303
+ API externe, requete DB, lecture disque). Sur ces operations, Python async
304
+ = Go en performance, a la milliseconde pres.
305
+
306
+ Le cout d'ajouter Go est massif :
307
+ - Nouvelle toolchain (`go build`, `go mod`)
308
+ - 2e langage a maintenir / debugger
309
+ - 2e binaire / image Docker / pipeline de deploy
310
+ - Communication inter-langage (queue ou cgo) avec sa propre complexite
311
+
312
+ Reserve Go pour le 1% de cas ou tu as **vraiment** mesure que Python CPU
313
+ est le bottleneck (parsing binaire intensif, math sur grands tenseurs).
314
+
315
+ ### 6.3 Pourquoi ARQ et pas Celery ?
316
+
317
+ | | Celery | ARQ |
318
+ |---|---|---|
319
+ | Age | 2009 | 2017 |
320
+ | Async natif | Non (sync, support async ajoute apres) | **Oui** |
321
+ | Broker | RabbitMQ/Redis/etc. | Redis uniquement |
322
+ | Taille code | Lourd | Leger (~3k lignes) |
323
+ | Battle-tested | Instagram, Mozilla, Pinterest | Plus modeste |
324
+ | Ecosysteme | Flower, beat, multiple plugins | Minimal |
325
+ | Cohabitation FastAPI | Demande plomberie | Naturelle |
326
+
327
+ ARQ est **conçu pour Python async** depuis le debut. Dans un projet ou
328
+ **tout** est `async def` (routes, services, dependances), Celery introduit
329
+ une rupture mentale (workers sync) ; ARQ reste coherent.
330
+
331
+ Pour des cas de scale extreme (millions de jobs/jour, multi-broker, monitoring
332
+ sophistique), Celery garde l'avantage. Pour 99% des projets, ARQ suffit
333
+ largement.
334
+
335
+ ### 6.4 Architecture cote API
336
+
337
+ Le pool Redis est cree dans le `lifespan` :
338
+
339
+ ```python
340
+ @asynccontextmanager
341
+ async def lifespan(app: FastAPI):
342
+ # ... create_all tables ...
343
+ try:
344
+ app.state.arq_pool = await create_pool(RedisSettings.from_dsn(settings.REDIS_URL))
345
+ except Exception as e:
346
+ logger.warning("Redis indisponible (%s) -- background tasks desactives", e)
347
+ app.state.arq_pool = None
348
+ yield
349
+ if app.state.arq_pool is not None:
350
+ await app.state.arq_pool.close()
351
+ ```
352
+
353
+ **Degradation gracieuse** : si Redis n'est pas joignable au demarrage, l'app
354
+ demarre quand meme. Les routes qui veulent enqueue retournent 503 via la
355
+ dependance `get_arq`. Le reste (admin, CRUD users, API) fonctionne sans
356
+ difference.
357
+
358
+ ### 6.5 Architecture cote worker
359
+
360
+ `worker.py` a la racine = entrypoint trivial qui appelle `run_worker(WorkerSettings)`.
361
+ `WorkerSettings` vit dans `src/tasks.py` (a cote des taches qu'il execute) :
362
+
363
+ - `functions: list` -> liste des fonctions appelables via `enqueue_job(name, ...)`
364
+ - `redis_settings` -> connexion Redis (meme URL que cote API)
365
+ - `max_jobs` -> concurrence par worker (10 par defaut)
366
+ - `job_timeout` -> timeout par tache (5 min par defaut)
367
+
368
+ Tu peux lancer N workers en parallele sur N machines : Redis joue le role
369
+ de broker partage. C'est exactement le pattern Celery sans la lourdeur.
370
+
371
+ ---
372
+
373
+ ## 7. Le mecanisme `test-self`
374
+
375
+ `cmd_test_self()` est la garantie que **Fabrik genere toujours un projet qui
376
+ demarre vraiment**. Le workflow :
377
+
378
+ 1. `tempfile.mkdtemp()` -> projet jetable
379
+ 2. `cmd_new(absolute_path, no_input=True)` -> generation complete
380
+ 3. Verifications : venv existe, `.scaffold-version` ecrit, migration appliquee
381
+ 4. `subprocess pytest tests/ -q` -> doit retourner 0
382
+ 5. `subprocess uvicorn main:app --port <random>` en background
383
+ 6. Attente de l'ouverture du port (timeout 25s)
384
+ 7. HTTP GET sur `/`, `/admin/login`, `/docs` -> doit retourner 200
385
+ 8. Kill du serveur
386
+ 9. `cmd_add("articles")` -> test du module + auto-wiring
387
+ 10. `ast.parse()` sur les 5 fichiers generes -> doit etre du Python valide
388
+ 11. `shutil.rmtree(tmp)` (sauf si `--keep`)
389
+
390
+ Le CI (`.github/workflows/ci.yml`) lance ce test a chaque push -- un commit
391
+ qui casse la generation se voit immediatement.
392
+
393
+ ---
394
+
395
+ ## 8. Limites connues
396
+
397
+ - **Pas de plugins externes.** Fabrik est volontairement monolithique. Le
398
+ jour ou tu veux brancher un module tiers (ex: OAuth Google), tu copies le
399
+ code dans `src/<module>/` au lieu de `pip install fabrik-plugin-X`.
400
+ - **Python 3.13+ obligatoire.** On utilise les nouveautes (PEP 695, etc.) et
401
+ on ne supporte pas les versions plus anciennes.
402
+ - **PostgreSQL recommande en prod.** SQLite marche pour le dev mais
403
+ manque de concurrence pour la production.
404
+ - **L'admin scanne TOUS les modeles SQLAlchemy.** Pas (encore) de mecanisme
405
+ pour exclure des modeles internes.
406
+
407
+ ---
408
+
409
+ ## 9. Ressources
410
+
411
+ - [README.md](README.md) : presentation generale
412
+ - [docs/USAGE.md](docs/USAGE.md) : guide utilisateur complet
413
+ - [scaffold.py](scaffold.py) : code source du moteur
414
+ - [core/](core/) : templates copies dans chaque projet
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Falandy Jean
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # Inclut tous les fichiers de /core/ dans la distribution source (sdist).
2
+ # Necessaire car setuptools n'inclut pas les non-Python par defaut.
3
+ recursive-include fabrik/core *
4
+ recursive-include fabrik/core/.* *
5
+
6
+ # Dotfiles dans core/ (essentiels pour les projets generes)
7
+ include fabrik/core/.gitignore
8
+ include fabrik/core/.env.example
9
+ include fabrik/core/.dockerignore
10
+ include fabrik/core/alembic/versions/.gitkeep
11
+
12
+ # Meta du repo (pour la sdist)
13
+ include README.md
14
+ include LICENSE
15
+ include ARCHITECTURE.md
16
+ include pyproject.toml
17
+ include MANIFEST.in
18
+ recursive-include docs *.md
19
+
20
+ # Exclusions
21
+ global-exclude __pycache__
22
+ global-exclude *.py[cod]
23
+ global-exclude *.bak
24
+ global-exclude .DS_Store
25
+ prune .github
26
+ prune tests
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: fabrik-fastapi
3
+ Version: 1.0.0
4
+ Summary: Generateur de projet FastAPI async + opinionated (auth JWT, admin UI, ARQ, tests)
5
+ Author-email: Falandy Jean <falandyjean@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/FalandyJEAN/fabrik
8
+ Project-URL: Repository, https://github.com/FalandyJEAN/fabrik
9
+ Project-URL: Documentation, https://github.com/FalandyJEAN/fabrik/blob/main/docs/USAGE.md
10
+ Project-URL: Issues, https://github.com/FalandyJEAN/fabrik/issues
11
+ Project-URL: Changelog, https://github.com/FalandyJEAN/fabrik/releases
12
+ Project-URL: Logo, https://raw.githubusercontent.com/FalandyJEAN/fabrik/main/docs/assets/logo.png
13
+ Keywords: fastapi,scaffold,generator,boilerplate,async,sqlalchemy,alembic,admin,jwt,cli
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Code Generators
22
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
24
+ Classifier: Framework :: FastAPI
25
+ Classifier: Environment :: Console
26
+ Requires-Python: >=3.13
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.0; extra == "dev"
31
+ Requires-Dist: twine>=5.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ <p align="center">
35
+ <img src="docs/assets/logo.png" alt="Fabrik logo" width="200" />
36
+ </p>
37
+
38
+ <h1 align="center">Fabrik</h1>
39
+
40
+ <p align="center">
41
+ <strong>Generateur de projet FastAPI async + opinionated.</strong><br>
42
+ En 60 secondes : auth JWT, admin UI auto-decouverte, tests isoles,<br>
43
+ background tasks (ARQ), migrations Alembic, CORS strict, responsive.
44
+ </p>
45
+
46
+ [![PyPI version](https://img.shields.io/pypi/v/fabrik-cli.svg)](https://pypi.org/project/fabrik-cli/)
47
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
48
+ [![Fabrik CI](https://github.com/FalandyJEAN/fabrik/actions/workflows/ci.yml/badge.svg)](https://github.com/FalandyJEAN/fabrik/actions/workflows/ci.yml)
49
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
50
+
51
+ **Version :** `1.0.0` &nbsp;&middot;&nbsp; **Auteur :** Falandy Jean &nbsp;&middot;&nbsp; **Licence :** MIT
52
+
53
+ ---
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install fabrik-cli
59
+ ```
60
+
61
+ Verifier l'installation :
62
+
63
+ ```bash
64
+ fabrik --help
65
+ ```
66
+
67
+ Pre-requis : **Python 3.13+**.
68
+
69
+ ---
70
+
71
+ ## 60 secondes pour demarrer
72
+
73
+ ```bash
74
+ fabrik new mon-api
75
+ cd mon-api
76
+ venv\Scripts\activate # Windows
77
+ # source venv/bin/activate # Linux/Mac
78
+ python create_superuser.py
79
+ python -m uvicorn main:app --reload
80
+ ```
81
+
82
+ Ouvre :
83
+ - **Admin UI** : http://127.0.0.1:8000/admin
84
+ - **Swagger** : http://127.0.0.1:8000/docs
85
+
86
+ ---
87
+
88
+ ## Ce que tu obtiens out-of-the-box
89
+
90
+ | Domaine | Choix par defaut |
91
+ |--------------------|---------------------------------------------------------------|
92
+ | Framework | FastAPI 0.136 + Starlette |
93
+ | ORM | SQLAlchemy 2.0 **async** (`AsyncSession`) |
94
+ | Driver DB | `aiosqlite` (dev) / `asyncpg` (prod PostgreSQL) |
95
+ | Auth | JWT (access + refresh) avec bcrypt |
96
+ | Admin | UI auto-decouverte responsive, multi-search, bulk delete, CSV |
97
+ | Migrations | Alembic (autogenerate) |
98
+ | Background tasks | ARQ (Redis-backed, async-native) avec degradation gracieuse |
99
+ | Tests | pytest + pytest-asyncio + DB SQLite in-memory isolee |
100
+ | CORS | Strict via `BACKEND_CORS_ORIGINS` (pas de `*`) |
101
+ | Config | `pydantic-settings` (12-factor) |
102
+ | Docker | `Dockerfile` Python 3.13-slim + `docker-compose.yml` |
103
+
104
+ ---
105
+
106
+ ## Les 4 commandes
107
+
108
+ | Commande | Effet |
109
+ |-------------------------------------|---------------------------------------------------------------|
110
+ | `fabrik new <nom>` | Genere un projet complet (venv + deps + migration initiale) |
111
+ | `fabrik add <module>` | Ajoute un module CRUD (auto-wire + migration + tests) |
112
+ | `fabrik upgrade` | Met a jour un projet existant a la derniere version |
113
+ | `fabrik test-self` | Meta-test : verifie que le scaffold genere un projet OK |
114
+
115
+ Detail complet dans [docs/USAGE.md](docs/USAGE.md).
116
+
117
+ ---
118
+
119
+ ## Pourquoi Fabrik ?
120
+
121
+ Fabrik couvre l'angle mort entre **Django** (rigide, monolithique, sync) et
122
+ **FastAPI nu** (zero opinion, 2 jours de plomberie a chaque projet).
123
+
124
+ | | FastAPI standard | Django | **Fabrik** |
125
+ |---|---|---|---|
126
+ | Stack async | oui | non | **oui** |
127
+ | Auth JWT prete | non | tierce | **oui** |
128
+ | Admin UI auto-genere | non | oui (sync) | **oui (async, responsive)** |
129
+ | Background tasks | non | Celery (tiers) | **oui (ARQ inclus)** |
130
+ | Tests isoles fournis | non | oui | **oui** |
131
+ | Architecture modulaire | a faire | apps | **`src/<module>/`** |
132
+ | Migration de projet | non | manuel | **`fabrik upgrade`** |
133
+ | Demarrage zero-config | 2j | 30 min | **60 secondes** |
134
+
135
+ Detail des choix de conception : [ARCHITECTURE.md](ARCHITECTURE.md).
136
+
137
+ ---
138
+
139
+ ## Structure d'un projet genere
140
+
141
+ ```
142
+ mon-api/
143
+ ├── main.py FastAPI app + lifespan + pool ARQ
144
+ ├── worker.py Entrypoint ARQ (python worker.py)
145
+ ├── docker-compose.yml Redis + (PostgreSQL optionnel)
146
+ ├── .env SECRET_KEY 256 bits + CORS + DB + Redis
147
+ ├── .scaffold-version Trace de la version Fabrik (pour upgrade)
148
+ ├── create_superuser.py
149
+ ├── requirements.txt
150
+ ├── pytest.ini
151
+ ├── alembic.ini + alembic/
152
+ ├── src/
153
+ │ ├── database.py AsyncEngine + get_db
154
+ │ ├── tasks.py ARQ tasks + WorkerSettings + get_arq dep
155
+ │ ├── core/ Config, security, mixins, pagination
156
+ │ ├── users/ Module exemple (models/schemas/service/router)
157
+ │ └── admin/ UI auto-decouverte (router + templates + static)
158
+ └── tests/
159
+ ├── conftest.py Fixtures async (DB in-memory isolee)
160
+ ├── test_users.py
161
+ └── test_tasks.py
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Architecture du repo Fabrik
167
+
168
+ ```
169
+ fabrik/ Repo GitHub
170
+ ├── pyproject.toml Package metadata (PyPI)
171
+ ├── MANIFEST.in
172
+ ├── README.md, ARCHITECTURE.md, LICENSE
173
+ ├── docs/
174
+ │ ├── USAGE.md Guide utilisateur
175
+ │ └── PUBLISHING.md Workflow de release PyPI
176
+ ├── .github/workflows/ci.yml CI : lance test-self a chaque commit
177
+ └── fabrik/ Package Python
178
+ ├── __init__.py
179
+ ├── __main__.py Pour `python -m fabrik`
180
+ ├── scaffold.py Moteur CLI
181
+ └── core/ Templates copies a chaque `new`
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Developpement local
187
+
188
+ Si tu veux contribuer ou tester en local sans publier sur PyPI :
189
+
190
+ ```bash
191
+ git clone https://github.com/FalandyJEAN/fabrik.git
192
+ cd fabrik
193
+ pip install -e . # installation en mode editable
194
+ fabrik test-self # verifie que tout fonctionne
195
+ ```
196
+
197
+ Tout changement qui modifie la structure des projets generes doit :
198
+
199
+ 1. Bumper `SCAFFOLD_VERSION` dans `fabrik/scaffold.py` (et `version` dans `pyproject.toml`)
200
+ 2. Ajouter une fonction `patch_vN_to_vM(root)` **idempotente**
201
+ 3. L'enregistrer dans `PATCHES`
202
+ 4. `fabrik test-self` doit passer en local et en CI
203
+
204
+ ---
205
+
206
+ ## Publication PyPI
207
+
208
+ Workflow complet dans [docs/PUBLISHING.md](docs/PUBLISHING.md). En resume :
209
+
210
+ ```bash
211
+ pip install -e ".[dev]"
212
+ python -m build
213
+ twine upload dist/*
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Licence
219
+
220
+ MIT &copy; 2026 Falandy Jean &mdash; voir [LICENSE](LICENSE).