python-flashapi 0.1.2__py3-none-any.whl → 0.2.0__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 (48) hide show
  1. flashapi/__init__.py +8 -7
  2. flashapi/adapters/base.py +13 -12
  3. flashapi/adapters/django.py +771 -165
  4. flashapi/adapters/fastapi.py +879 -293
  5. flashapi/adapters/flask.py +728 -232
  6. flashapi/core/__init__.py +11 -3
  7. flashapi/core/custom_routes.py +5 -5
  8. flashapi/core/pluralize.py +80 -80
  9. flashapi/core/relations.py +59 -61
  10. flashapi/core/response.py +36 -33
  11. flashapi/core/schema.py +145 -83
  12. flashapi/core/visibility.py +46 -0
  13. flashapi/django.py +5 -5
  14. flashapi/docs/openapi.py +298 -223
  15. flashapi/fastapi.py +5 -5
  16. flashapi/features/__init__.py +6 -6
  17. flashapi/features/audit.py +84 -0
  18. flashapi/features/auth.py +134 -0
  19. flashapi/features/dashboard.py +412 -0
  20. flashapi/features/export.py +143 -0
  21. flashapi/features/filtering.py +124 -33
  22. flashapi/features/pagination.py +20 -20
  23. flashapi/features/rate_limit.py +45 -0
  24. flashapi/features/search.py +25 -25
  25. flashapi/features/sorting.py +23 -21
  26. flashapi/features/webhooks.py +84 -0
  27. flashapi/features/websocket.py +129 -0
  28. flashapi/flask.py +5 -5
  29. flashapi/inspectors/__init__.py +3 -3
  30. flashapi/inspectors/base.py +13 -11
  31. flashapi/inspectors/dataclass.py +50 -39
  32. flashapi/inspectors/detect.py +54 -48
  33. flashapi/inspectors/django.py +93 -83
  34. flashapi/inspectors/pydantic.py +99 -84
  35. flashapi/inspectors/sqlalchemy.py +83 -75
  36. flashapi/storage/__init__.py +4 -4
  37. flashapi/storage/auto.py +189 -106
  38. flashapi/storage/base.py +32 -26
  39. flashapi/storage/orm.py +125 -85
  40. flashapi/storage/sqlalchemy.py +56 -13
  41. python_flashapi-0.2.0.dist-info/METADATA +314 -0
  42. python_flashapi-0.2.0.dist-info/RECORD +48 -0
  43. python_flashapi-0.2.0.dist-info/licenses/LICENSE +190 -0
  44. python_flashapi-0.2.0.dist-info/licenses/NOTICE +5 -0
  45. python_flashapi-0.1.2.dist-info/METADATA +0 -259
  46. python_flashapi-0.1.2.dist-info/RECORD +0 -39
  47. python_flashapi-0.1.2.dist-info/licenses/LICENSE +0 -21
  48. {python_flashapi-0.1.2.dist-info → python_flashapi-0.2.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,46 @@
1
+ """Field visibility rules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from flashapi.core.schema import ModelSchema
9
+
10
+
11
+ def response_fields(schema: ModelSchema) -> set[str]:
12
+ """Fields visible in GET responses."""
13
+ return {
14
+ f.name for f in schema.fields
15
+ if not f.hidden and not f.writeonly
16
+ }
17
+
18
+
19
+ def writable_fields(schema: ModelSchema) -> set[str]:
20
+ """Fields accepted in POST/PUT bodies."""
21
+ return {
22
+ f.name for f in schema.fields
23
+ if not f.hidden and not f.readonly and not f.primary_key
24
+ and not f.auto_generated and not f.auto
25
+ }
26
+
27
+
28
+ def export_fields(schema: ModelSchema) -> set[str]:
29
+ """Fields included in exports."""
30
+ return {
31
+ f.name for f in schema.fields
32
+ if not f.hidden and not f.writeonly and not f.export_exclude
33
+ }
34
+
35
+
36
+ def filter_response(data: dict, schema: ModelSchema) -> dict:
37
+ """Remove hidden/writeonly fields from a response dict."""
38
+ visible = response_fields(schema)
39
+ schema_field_names = {f.name for f in schema.fields}
40
+ return {k: v for k, v in data.items() if k in visible or k == "id" or k not in schema_field_names}
41
+
42
+
43
+ def filter_input(data: dict, schema: ModelSchema) -> dict:
44
+ """Remove readonly/hidden fields from input dict."""
45
+ allowed = writable_fields(schema)
46
+ return {k: v for k, v in data.items() if k in allowed}
flashapi/django.py CHANGED
@@ -1,5 +1,5 @@
1
- """FlashAPI Django adapter — public entry point."""
2
-
3
- from flashapi.adapters.django import generate_urls
4
-
5
- __all__ = ["generate_urls"]
1
+ """FlashAPI Django adapter — public entry point."""
2
+
3
+ from flashapi.adapters.django import generate_urls
4
+
5
+ __all__ = ["generate_urls"]
flashapi/docs/openapi.py CHANGED
@@ -1,223 +1,298 @@
1
- """OpenAPI schema generation and Swagger UI for Flask and Django."""
2
-
3
- from typing import Any
4
-
5
- from flashapi.core.schema import ModelSchema, FieldType
6
-
7
- FIELD_TYPE_TO_OPENAPI = {
8
- FieldType.STRING: {"type": "string"},
9
- FieldType.INTEGER: {"type": "integer"},
10
- FieldType.FLOAT: {"type": "number"},
11
- FieldType.BOOLEAN: {"type": "boolean"},
12
- FieldType.DATE: {"type": "string", "format": "date"},
13
- FieldType.DATETIME: {"type": "string", "format": "date-time"},
14
- FieldType.TIME: {"type": "string", "format": "time"},
15
- FieldType.UUID: {"type": "string", "format": "uuid"},
16
- FieldType.JSON: {"type": "object"},
17
- FieldType.TEXT: {"type": "string"},
18
- FieldType.BINARY: {"type": "string", "format": "binary"},
19
- }
20
-
21
- SWAGGER_UI_HTML = """<!DOCTYPE html>
22
- <html>
23
- <head>
24
- <title>{title} - Docs</title>
25
- <meta charset="utf-8"/>
26
- <meta name="viewport" content="width=device-width, initial-scale=1">
27
- <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
28
- </head>
29
- <body>
30
- <div id="swagger-ui"></div>
31
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
32
- <script>
33
- SwaggerUIBundle({{
34
- url: "{openapi_url}",
35
- dom_id: '#swagger-ui',
36
- presets: [
37
- SwaggerUIBundle.presets.apis,
38
- SwaggerUIBundle.SwaggerUIStandalonePreset
39
- ],
40
- layout: "BaseLayout"
41
- }})
42
- </script>
43
- </body>
44
- </html>"""
45
-
46
-
47
- def generate_openapi_schema(
48
- schemas: list[ModelSchema],
49
- title: str = "FlashAPI",
50
- version: str = "0.1.0",
51
- description: str = "Define your models. FlashAPI does the rest.",
52
- trailing_slash: bool = False,
53
- ) -> dict[str, Any]:
54
- """Generate a full OpenAPI 3.1.0 schema from model schemas."""
55
- paths = {}
56
- components_schemas = {}
57
-
58
- for schema in schemas:
59
- components_schemas[schema.name] = _build_model_schema(schema)
60
- components_schemas[f"{schema.name}Create"] = _build_model_schema(schema, exclude_auto_pk=True)
61
- model_paths = _build_paths(schema, trailing_slash=trailing_slash)
62
- paths.update(model_paths)
63
-
64
- return {
65
- "openapi": "3.1.0",
66
- "info": {
67
- "title": title,
68
- "version": version,
69
- "description": description,
70
- },
71
- "paths": paths,
72
- "components": {
73
- "schemas": components_schemas,
74
- },
75
- }
76
-
77
-
78
- def _build_model_schema(schema: ModelSchema, *, exclude_auto_pk: bool = False) -> dict:
79
- properties = {}
80
- required = []
81
-
82
- for field in schema.fields:
83
- if exclude_auto_pk and field.primary_key and field.auto_generated:
84
- continue
85
- prop = dict(FIELD_TYPE_TO_OPENAPI.get(field.type, {"type": "string"}))
86
- if field.constraints.get("max_length"):
87
- prop["maxLength"] = field.constraints["max_length"]
88
- if field.constraints.get("min_length"):
89
- prop["minLength"] = field.constraints["min_length"]
90
- if field.constraints.get("min_value") is not None:
91
- prop["minimum"] = field.constraints["min_value"]
92
- if field.constraints.get("max_value") is not None:
93
- prop["maximum"] = field.constraints["max_value"]
94
- properties[field.name] = prop
95
- if field.required and not field.primary_key:
96
- required.append(field.name)
97
-
98
- result = {"type": "object", "properties": properties}
99
- if required:
100
- result["required"] = required
101
- return result
102
-
103
-
104
- def _build_paths(schema: ModelSchema, trailing_slash: bool = False) -> dict:
105
- paths = {}
106
- table = schema.plural
107
- tag = schema.name
108
- suffix = "/" if trailing_slash else ""
109
-
110
- collection_ops = {}
111
- detail_ops = {}
112
-
113
- if "list" in schema.permissions:
114
- collection_ops["get"] = {
115
- "tags": [tag],
116
- "summary": f"List all {table}",
117
- "parameters": [
118
- {"name": "page", "in": "query", "schema": {"type": "integer", "default": 1}},
119
- {"name": "page_size", "in": "query", "schema": {"type": "integer", "default": 20}},
120
- {"name": "sort", "in": "query", "schema": {"type": "string"}},
121
- {"name": "search", "in": "query", "schema": {"type": "string"}},
122
- ],
123
- "responses": {
124
- "200": {
125
- "description": "Paginated list",
126
- "content": {"application/json": {"schema": {
127
- "type": "object",
128
- "properties": {
129
- "data": {"type": "array", "items": {"$ref": f"#/components/schemas/{schema.name}"}},
130
- "total": {"type": "integer"},
131
- "page": {"type": "integer"},
132
- "pages": {"type": "integer"},
133
- "page_size": {"type": "integer"},
134
- }
135
- }}}
136
- }
137
- }
138
- }
139
-
140
- if "create" in schema.permissions:
141
- collection_ops["post"] = {
142
- "tags": [tag],
143
- "summary": f"Create a {schema.name.lower()}",
144
- "requestBody": {
145
- "required": True,
146
- "content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{schema.name}Create"}}}
147
- },
148
- "responses": {
149
- "201": {
150
- "description": "Created",
151
- "content": {"application/json": {"schema": {
152
- "type": "object",
153
- "properties": {"data": {"$ref": f"#/components/schemas/{schema.name}"}}
154
- }}}
155
- }
156
- }
157
- }
158
-
159
- if "read" in schema.permissions:
160
- detail_ops["get"] = {
161
- "tags": [tag],
162
- "summary": f"Get a {schema.name.lower()} by ID",
163
- "parameters": [
164
- {"name": "item_id", "in": "path", "required": True, "schema": {"type": "integer"}}
165
- ],
166
- "responses": {
167
- "200": {
168
- "description": "Item detail",
169
- "content": {"application/json": {"schema": {
170
- "type": "object",
171
- "properties": {"data": {"$ref": f"#/components/schemas/{schema.name}"}}
172
- }}}
173
- },
174
- "404": {"description": "Not found"}
175
- }
176
- }
177
-
178
- if "update" in schema.permissions:
179
- detail_ops["put"] = {
180
- "tags": [tag],
181
- "summary": f"Update a {schema.name.lower()}",
182
- "parameters": [
183
- {"name": "item_id", "in": "path", "required": True, "schema": {"type": "integer"}}
184
- ],
185
- "requestBody": {
186
- "required": True,
187
- "content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{schema.name}Create"}}}
188
- },
189
- "responses": {
190
- "200": {
191
- "description": "Updated",
192
- "content": {"application/json": {"schema": {
193
- "type": "object",
194
- "properties": {"data": {"$ref": f"#/components/schemas/{schema.name}"}}
195
- }}}
196
- },
197
- "404": {"description": "Not found"}
198
- }
199
- }
200
-
201
- if "delete" in schema.permissions:
202
- detail_ops["delete"] = {
203
- "tags": [tag],
204
- "summary": f"Delete a {schema.name.lower()}",
205
- "parameters": [
206
- {"name": "item_id", "in": "path", "required": True, "schema": {"type": "integer"}}
207
- ],
208
- "responses": {
209
- "204": {"description": "Deleted"},
210
- "404": {"description": "Not found"}
211
- }
212
- }
213
-
214
- if collection_ops:
215
- paths[f"/{table}{suffix}"] = collection_ops
216
- if detail_ops:
217
- paths[f"/{table}/{{item_id}}{suffix}"] = detail_ops
218
-
219
- return paths
220
-
221
-
222
- def get_swagger_html(title: str = "FlashAPI", openapi_url: str = "/openapi.json") -> str:
223
- return SWAGGER_UI_HTML.format(title=title, openapi_url=openapi_url)
1
+ """OpenAPI schema generation and Swagger UI for Flask and Django."""
2
+
3
+ from typing import Any
4
+
5
+ from flashapi.core.schema import FieldType, ModelSchema
6
+
7
+ FIELD_TYPE_TO_OPENAPI = {
8
+ FieldType.STRING: {"type": "string"},
9
+ FieldType.INTEGER: {"type": "integer"},
10
+ FieldType.FLOAT: {"type": "number"},
11
+ FieldType.BOOLEAN: {"type": "boolean"},
12
+ FieldType.DATE: {"type": "string", "format": "date"},
13
+ FieldType.DATETIME: {"type": "string", "format": "date-time"},
14
+ FieldType.TIME: {"type": "string", "format": "time"},
15
+ FieldType.UUID: {"type": "string", "format": "uuid"},
16
+ FieldType.JSON: {"type": "object"},
17
+ FieldType.TEXT: {"type": "string"},
18
+ FieldType.BINARY: {"type": "string", "format": "binary"},
19
+ }
20
+
21
+ SWAGGER_UI_HTML = """<!DOCTYPE html>
22
+ <html>
23
+ <head>
24
+ <title>{title} - Docs</title>
25
+ <meta charset="utf-8"/>
26
+ <meta name="viewport" content="width=device-width, initial-scale=1">
27
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
28
+ </head>
29
+ <body>
30
+ <div id="swagger-ui"></div>
31
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
32
+ <script>
33
+ SwaggerUIBundle({{
34
+ url: "{openapi_url}",
35
+ dom_id: '#swagger-ui',
36
+ presets: [
37
+ SwaggerUIBundle.presets.apis,
38
+ SwaggerUIBundle.SwaggerUIStandalonePreset
39
+ ],
40
+ layout: "BaseLayout"
41
+ }})
42
+ </script>
43
+ </body>
44
+ </html>"""
45
+
46
+
47
+ def generate_openapi_schema(
48
+ schemas: list[ModelSchema],
49
+ title: str = "FlashAPI",
50
+ version: str = "0.1.0",
51
+ description: str = "Define your models. FlashAPI does the rest.",
52
+ trailing_slash: bool = False,
53
+ ) -> dict[str, Any]:
54
+ """Generate a full OpenAPI 3.1.0 schema from model schemas."""
55
+ paths = {}
56
+ components_schemas = {}
57
+
58
+ for schema in schemas:
59
+ components_schemas[schema.name] = _build_model_schema(schema)
60
+ components_schemas[f"{schema.name}Create"] = _build_model_schema(schema, exclude_auto=True)
61
+ model_paths = _build_paths(schema, trailing_slash=trailing_slash)
62
+ paths.update(model_paths)
63
+
64
+ return {
65
+ "openapi": "3.1.0",
66
+ "info": {
67
+ "title": title,
68
+ "version": version,
69
+ "description": description,
70
+ },
71
+ "paths": paths,
72
+ "components": {
73
+ "schemas": components_schemas,
74
+ },
75
+ }
76
+
77
+
78
+ def _build_model_schema(schema: ModelSchema, *, exclude_auto: bool = False) -> dict:
79
+ properties = {}
80
+ required = []
81
+
82
+ for field in schema.fields:
83
+ if exclude_auto and field.auto_generated:
84
+ continue
85
+ prop = dict(FIELD_TYPE_TO_OPENAPI.get(field.type, {"type": "string"}))
86
+ if field.constraints.get("max_length"):
87
+ prop["maxLength"] = field.constraints["max_length"]
88
+ if field.constraints.get("min_length"):
89
+ prop["minLength"] = field.constraints["min_length"]
90
+ if field.constraints.get("min_value") is not None:
91
+ prop["minimum"] = field.constraints["min_value"]
92
+ if field.constraints.get("max_value") is not None:
93
+ prop["maximum"] = field.constraints["max_value"]
94
+ properties[field.name] = prop
95
+ if field.required and not field.primary_key:
96
+ required.append(field.name)
97
+
98
+ result = {"type": "object", "properties": properties}
99
+ if required:
100
+ result["required"] = required
101
+ return result
102
+
103
+
104
+ def _resolve_lookup_type(schema: ModelSchema) -> dict:
105
+ """Get the OpenAPI type for the lookup field (item_id parameter)."""
106
+ lf = schema.lookup_field or "id"
107
+ for field in schema.fields:
108
+ if field.name == lf or (field.primary_key and lf == "id"):
109
+ return dict(FIELD_TYPE_TO_OPENAPI.get(field.type, {"type": "string"}))
110
+ return {"type": "string"}
111
+
112
+
113
+ def _build_paths(schema: ModelSchema, trailing_slash: bool = False) -> dict:
114
+ paths = {}
115
+ table = schema.plural
116
+ tag = schema.name
117
+ suffix = "/" if trailing_slash else ""
118
+ lookup_schema = _resolve_lookup_type(schema)
119
+
120
+ collection_ops = {}
121
+ detail_ops = {}
122
+
123
+ if "list" in schema.permissions:
124
+ collection_ops["get"] = {
125
+ "tags": [tag],
126
+ "summary": f"List all {table}",
127
+ "parameters": [
128
+ {"name": "page", "in": "query", "schema": {"type": "integer", "default": 0}},
129
+ {"name": "size", "in": "query", "schema": {"type": "integer", "default": 20}},
130
+ {"name": "sort", "in": "query", "schema": {"type": "string"}},
131
+ {"name": "search", "in": "query", "schema": {"type": "string"}},
132
+ {"name": "deleted", "in": "query", "schema": {"type": "boolean", "default": False}},
133
+ {"name": "expand", "in": "query", "schema": {"type": "string"}},
134
+ ],
135
+ "responses": {
136
+ "200": {
137
+ "description": "Paginated list",
138
+ "content": {"application/json": {"schema": {
139
+ "type": "object",
140
+ "properties": {
141
+ "data": {"type": "array", "items": {"$ref": f"#/components/schemas/{schema.name}"}},
142
+ "meta": {"type": "object"},
143
+ },
144
+ }}},
145
+ },
146
+ },
147
+ }
148
+
149
+ if "create" in schema.permissions:
150
+ collection_ops["post"] = {
151
+ "tags": [tag],
152
+ "summary": f"Create a {schema.name.lower()}",
153
+ "requestBody": {
154
+ "required": True,
155
+ "content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{schema.name}Create"}}},
156
+ },
157
+ "responses": {
158
+ "201": {
159
+ "description": "Created",
160
+ "content": {"application/json": {"schema": {
161
+ "type": "object",
162
+ "properties": {"data": {"$ref": f"#/components/schemas/{schema.name}"}},
163
+ }}},
164
+ },
165
+ },
166
+ }
167
+
168
+ if "read" in schema.permissions:
169
+ detail_ops["get"] = {
170
+ "tags": [tag],
171
+ "summary": f"Get a {schema.name.lower()} by ID",
172
+ "parameters": [
173
+ {"name": "item_id", "in": "path", "required": True, "schema": lookup_schema},
174
+ {"name": "expand", "in": "query", "schema": {"type": "string"}},
175
+ ],
176
+ "responses": {
177
+ "200": {
178
+ "description": "Item detail",
179
+ "content": {"application/json": {"schema": {
180
+ "type": "object",
181
+ "properties": {"data": {"$ref": f"#/components/schemas/{schema.name}"}},
182
+ }}},
183
+ },
184
+ "404": {"description": "Not found"},
185
+ },
186
+ }
187
+
188
+ if "update" in schema.permissions:
189
+ detail_ops["put"] = {
190
+ "tags": [tag],
191
+ "summary": f"Update a {schema.name.lower()}",
192
+ "parameters": [
193
+ {"name": "item_id", "in": "path", "required": True, "schema": lookup_schema},
194
+ ],
195
+ "requestBody": {
196
+ "required": True,
197
+ "content": {"application/json": {"schema": {"$ref": f"#/components/schemas/{schema.name}Create"}}},
198
+ },
199
+ "responses": {
200
+ "200": {
201
+ "description": "Updated",
202
+ "content": {"application/json": {"schema": {
203
+ "type": "object",
204
+ "properties": {"data": {"$ref": f"#/components/schemas/{schema.name}"}},
205
+ }}},
206
+ },
207
+ "404": {"description": "Not found"},
208
+ },
209
+ }
210
+
211
+ if "delete" in schema.permissions:
212
+ detail_ops["delete"] = {
213
+ "tags": [tag],
214
+ "summary": f"{'Soft delete' if schema.soft_delete else 'Delete'} a {schema.name.lower()}",
215
+ "parameters": [
216
+ {"name": "item_id", "in": "path", "required": True, "schema": lookup_schema},
217
+ ],
218
+ "responses": {
219
+ "204": {"description": "Deleted"},
220
+ "404": {"description": "Not found"},
221
+ },
222
+ }
223
+
224
+ if collection_ops:
225
+ paths[f"/{table}{suffix}"] = collection_ops
226
+ if detail_ops:
227
+ paths[f"/{table}/{{item_id}}{suffix}"] = detail_ops
228
+
229
+ if "delete" in schema.permissions and schema.soft_delete:
230
+ paths[f"/{table}/{{item_id}}/restore{suffix}"] = {
231
+ "post": {
232
+ "tags": [tag],
233
+ "summary": f"Restore soft-deleted {schema.name.lower()}",
234
+ "parameters": [
235
+ {"name": "item_id", "in": "path", "required": True, "schema": lookup_schema},
236
+ ],
237
+ "responses": {
238
+ "204": {"description": "Restored"},
239
+ "404": {"description": "Not found"},
240
+ },
241
+ },
242
+ }
243
+
244
+ if "create" in schema.permissions:
245
+ paths[f"/{table}/bulk{suffix}"] = {
246
+ "post": {
247
+ "tags": [tag],
248
+ "summary": f"Bulk create {table}",
249
+ "requestBody": {
250
+ "required": True,
251
+ "content": {"application/json": {"schema": {
252
+ "type": "array",
253
+ "items": {"$ref": f"#/components/schemas/{schema.name}Create"},
254
+ }}},
255
+ },
256
+ "responses": {
257
+ "201": {
258
+ "description": "Bulk created",
259
+ "content": {"application/json": {"schema": {"type": "object"}}},
260
+ },
261
+ },
262
+ },
263
+ }
264
+
265
+ if "list" in schema.permissions:
266
+ paths[f"/{table}/export{suffix}"] = {
267
+ "get": {
268
+ "tags": [tag],
269
+ "summary": f"Export {table}",
270
+ "parameters": [
271
+ {"name": "format", "in": "query", "schema": {"type": "string", "default": "csv"}},
272
+ ],
273
+ "responses": {
274
+ "200": {"description": "Export file"},
275
+ },
276
+ },
277
+ }
278
+
279
+ if "read" in schema.permissions and schema.audit:
280
+ paths[f"/{table}/{{item_id}}/history{suffix}"] = {
281
+ "get": {
282
+ "tags": [tag],
283
+ "summary": f"Audit history for {schema.name.lower()}",
284
+ "parameters": [
285
+ {"name": "item_id", "in": "path", "required": True, "schema": lookup_schema},
286
+ ],
287
+ "responses": {
288
+ "200": {"description": "Audit history"},
289
+ },
290
+ },
291
+ }
292
+
293
+ return paths
294
+
295
+
296
+
297
+ def get_swagger_html(title: str = "FlashAPI", openapi_url: str = "/openapi.json") -> str:
298
+ return SWAGGER_UI_HTML.format(title=title, openapi_url=openapi_url)
flashapi/fastapi.py CHANGED
@@ -1,5 +1,5 @@
1
- """FlashAPI FastAPI adapter — public entry point."""
2
-
3
- from flashapi.adapters.fastapi import FlashAPI
4
-
5
- __all__ = ["FlashAPI"]
1
+ """FlashAPI FastAPI adapter — public entry point."""
2
+
3
+ from flashapi.adapters.fastapi import FlashAPI
4
+
5
+ __all__ = ["FlashAPI"]
@@ -1,6 +1,6 @@
1
- from flashapi.features.pagination import paginate
2
- from flashapi.features.filtering import apply_filters
3
- from flashapi.features.sorting import apply_sorting
4
- from flashapi.features.search import apply_search
5
-
6
- __all__ = ["paginate", "apply_filters", "apply_sorting", "apply_search"]
1
+ from flashapi.features.filtering import apply_filters
2
+ from flashapi.features.pagination import paginate
3
+ from flashapi.features.search import apply_search
4
+ from flashapi.features.sorting import apply_sorting
5
+
6
+ __all__ = ["apply_filters", "apply_search", "apply_sorting", "paginate"]