apiforge-analyzer 1.3.7 → 1.3.8

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # apiforge-analyzer
2
2
 
3
- CLI and library for auto-generating API documentation from live Express, Fastify, NestJS, Next.js, Vite, and React Router apps.
3
+ CLI and library for auto-generating API documentation from live Express, Fastify, NestJS, Next.js, Vite, and React Router apps — and Python FastAPI, Flask, and Django apps.
4
4
 
5
5
  Routes are extracted at runtime by walking the framework's internal route stack — no static analysis, no decorators, no changes to your production code.
6
6
 
@@ -260,6 +260,64 @@ await analyzeReactRouter({
260
260
  })
261
261
  ```
262
262
 
263
+ ### Python — FastAPI, Flask, Django / DRF
264
+
265
+ Run `npx apiforge` from the Python project root (Node ≥ 18 is still needed to run the CLI).
266
+ The project is recognised from `manage.py` or from `fastapi` / `flask` / `django` in
267
+ `requirements.txt`, `pyproject.toml`, `Pipfile`, `setup.py`, or `setup.cfg`.
268
+
269
+ It works like the Node flow. The CLI:
270
+
271
+ 1. Finds the file to patch: the module that creates the app (`main.py`, the Flask `create_app` module), or
272
+ Django's `manage.py`.
273
+ 2. Checks that the framework is installed. If it isn't, it offers to create `.venv` and `pip install` the
274
+ project's dependencies from `requirements.txt`, `setup.py`/`pyproject.toml` (`pip install -e .`), or a
275
+ bare `pyproject.toml` dependency list. This is the `npm install` step, done for you.
276
+ 3. Asks which command starts your server, like the npm-script picker:
277
+ - FastAPI: `fastapi dev`, `fastapi run`, `uvicorn main:app`, `python main.py`
278
+ - Flask: `flask --app app run`, `python app.py`
279
+ - Django: `manage.py runserver --noreload`, `manage.py runserver`
280
+
281
+ You can also type your own. All of these use the project's venv Python.
282
+ 4. Adds a temporary snippet to that file and runs the command. The snippet reads the routes as soon as
283
+ the app is loaded, before the server binds its port.
284
+ 5. If the server crashes with `No module named 'x'`, offers to install the package and retry. Only packages
285
+ the project declares (including extras such as `[test]`) or well-known import≠pip names (`jose` →
286
+ `python-jose`) are offered.
287
+ 6. Stops the server and uploads the routes. At the end it asks "Remove APIForge code changes?", like the
288
+ Node flow. Ctrl+C removes the snippet immediately.
289
+
290
+ The snippet holds no API key and does nothing unless the CLI runs it: it needs an `APIFORGE_INTROSPECT`
291
+ environment variable that only the CLI sets. It writes routes to a temp file outside the project, and the
292
+ CLI uploads them. If you keep the snippet, it does nothing on its own, and the next run replaces it.
293
+ Because your own start command runs, the server needs what it normally needs, such as env vars like a
294
+ database URL. If it fails, the CLI shows the last lines of its output, where Python prints the actual error.
295
+
296
+ The routes are read from the framework's live route table:
297
+
298
+ | Framework | Source of truth |
299
+ |---|---|
300
+ | FastAPI | `app.openapi()`, which gives real Pydantic types for params, bodies, and responses. It also picks up dependency names, `HTTPException` codes raised in handlers, and `app.mount()` sub-apps. |
301
+ | Flask | `app.url_map` plus each view's source: `request.args`, `request.get_json()`, `abort()`, decorators. Blueprints and `MethodView` are supported. |
302
+ | Django | The root URLconf resolver, including `path()`/`re_path()`, class-based views, DRF ViewSets/routers, `@api_view`, serializers, and `permission_classes`. Admin and DRF login URLs are skipped. |
303
+
304
+ The suggested commands use `APIFORGE_PYTHON`, else the active virtualenv, else `.venv/`, `venv/` or `env/`
305
+ in the project, else the system `python`. That interpreter must have the app's dependencies installed.
306
+
307
+ Programmatic use from Node doesn't inject anything or start a server. It imports the app with that same
308
+ interpreter and reads the route table directly:
309
+
310
+ ```ts
311
+ import { analyze } from 'apiforge-analyzer'
312
+
313
+ await analyze(null, {
314
+ projectName: 'My API',
315
+ framework: 'fastapi', // or 'flask' / 'django' — auto-detected when omitted
316
+ pythonApp: 'main:app', // Flask factory: 'app:create_app' + pythonFactory: true
317
+ // djangoSettings: 'mysite.settings', // Django: defaults to the one manage.py sets
318
+ })
319
+ ```
320
+
263
321
  ## Options
264
322
 
265
323
  ```ts
@@ -288,12 +346,18 @@ interface AnalyzerOptions {
288
346
  errorCatalogPath?: string
289
347
  envelopes?: Record<string, { wrapper: string; dataField: string; excluded?: string[] } | { bare: true }>
290
348
  annotationsPath?: string
349
+
350
+ // Python only — see "Python" above
351
+ pythonApp?: string // app import path, e.g. 'main:app' (auto-detected when omitted)
352
+ pythonFactory?: boolean // call pythonApp as a factory (Flask create_app)
353
+ pythonPath?: string // interpreter (default: APIFORGE_PYTHON / active venv / project venv)
354
+ djangoSettings?: string // e.g. 'mysite.settings' (default: read from manage.py)
291
355
  }
292
356
  ```
293
357
 
294
358
  `analyze()` throws instead of resolving to `[]` when the detected or forced framework has no
295
359
  analyzer (currently: anything other than `express`, `fastify`, `nestjs`, `nextjs-pages`,
296
- `nextjs-app`, `vite`, `react-router`). Every other early-exit (no API key, debounced, zero
360
+ `nextjs-app`, `vite`, `react-router`, `fastapi`, `flask`, `django`). Every other early-exit (no API key, debounced, zero
297
361
  routes found, `NODE_ENV=production`) still resolves to `[]` — only "this framework cannot be
298
362
  analyzed" is a thrown error, so a caller can tell the two apart. `detectFramework()` also
299
363
  exposes this directly: `detectFramework(app).supported` is `false` for a framework it can