fastapi-fsp 0.2.3__py3-none-any.whl → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-fsp
3
- Version: 0.2.3
3
+ Version: 0.4.0
4
4
  Summary: Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel
5
5
  Project-URL: Homepage, https://github.com/fromej-dev/fastapi-fsp
6
6
  Project-URL: Repository, https://github.com/fromej-dev/fastapi-fsp
@@ -205,6 +205,164 @@ GET /heroes/?field=full_name&operator=starts_with&value=Spider&field=age&operato
205
205
  - The field should be declared as `ClassVar[type]` in the SQLModel base class to work with Pydantic
206
206
  - Only computed fields with SQL expressions are supported; Python-only properties cannot be filtered at the database level
207
207
 
208
+ ## FilterBuilder API
209
+
210
+ For programmatic filter creation, use the fluent `FilterBuilder` API:
211
+
212
+ ```python
213
+ from fastapi_fsp import FilterBuilder
214
+
215
+ # Instead of manually creating Filter objects:
216
+ # filters = [
217
+ # Filter(field="age", operator=FilterOperator.GTE, value="30"),
218
+ # Filter(field="city", operator=FilterOperator.EQ, value="Chicago"),
219
+ # ]
220
+
221
+ # Use the builder pattern:
222
+ filters = (
223
+ FilterBuilder()
224
+ .where("age").gte(30)
225
+ .where("city").eq("Chicago")
226
+ .where("active").eq(True)
227
+ .where("tags").in_(["python", "fastapi"])
228
+ .where("created_at").between("2024-01-01", "2024-12-31")
229
+ .build()
230
+ )
231
+
232
+ # Use with FSPManager
233
+ @app.get("/heroes/")
234
+ def read_heroes(session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
235
+ additional_filters = FilterBuilder().where("deleted").eq(False).build()
236
+ fsp.with_filters(additional_filters)
237
+ return fsp.generate_response(select(Hero), session)
238
+ ```
239
+
240
+ ### Available FilterBuilder Methods
241
+
242
+ | Method | Description |
243
+ |--------|-------------|
244
+ | `.eq(value)` | Equal to |
245
+ | `.ne(value)` | Not equal to |
246
+ | `.gt(value)` | Greater than |
247
+ | `.gte(value)` | Greater than or equal |
248
+ | `.lt(value)` | Less than |
249
+ | `.lte(value)` | Less than or equal |
250
+ | `.like(pattern)` | Case-sensitive LIKE |
251
+ | `.ilike(pattern)` | Case-insensitive LIKE |
252
+ | `.in_(values)` | IN list |
253
+ | `.not_in(values)` | NOT IN list |
254
+ | `.between(low, high)` | BETWEEN range |
255
+ | `.is_null()` | IS NULL |
256
+ | `.is_not_null()` | IS NOT NULL |
257
+ | `.starts_with(prefix)` | Starts with (case-insensitive) |
258
+ | `.ends_with(suffix)` | Ends with (case-insensitive) |
259
+ | `.contains(substring)` | Contains (case-insensitive) |
260
+
261
+ ## Common Filter Presets
262
+
263
+ For frequently used filter patterns, use `CommonFilters`:
264
+
265
+ ```python
266
+ from fastapi_fsp import CommonFilters
267
+
268
+ # Active (non-deleted) records
269
+ filters = CommonFilters.active() # deleted=false
270
+
271
+ # Recent records (last 7 days)
272
+ filters = CommonFilters.recent(days=7)
273
+
274
+ # Date range
275
+ filters = CommonFilters.date_range(start=datetime(2024, 1, 1), end=datetime(2024, 12, 31))
276
+
277
+ # Records created today
278
+ filters = CommonFilters.today()
279
+
280
+ # Null checks
281
+ filters = CommonFilters.not_null("email")
282
+ filters = CommonFilters.is_null("deleted_at")
283
+
284
+ # Search
285
+ filters = CommonFilters.search("name", "john", match_type="contains")
286
+
287
+ # Combine presets
288
+ filters = CommonFilters.active() + CommonFilters.recent(days=30)
289
+ ```
290
+
291
+ ## Configuration
292
+
293
+ Customize FSPManager behavior with `FSPConfig`:
294
+
295
+ ```python
296
+ from fastapi_fsp import FSPConfig, FSPPresets
297
+
298
+ # Custom configuration
299
+ config = FSPConfig(
300
+ max_per_page=50,
301
+ default_per_page=20,
302
+ strict_mode=True, # Raise errors for unknown fields
303
+ max_page=100,
304
+ allow_deep_pagination=False,
305
+ )
306
+
307
+ # Or use presets
308
+ config = FSPPresets.strict() # strict_mode=True
309
+ config = FSPPresets.limited_pagination(max_page=50) # Limit deep pagination
310
+ config = FSPPresets.high_volume(max_per_page=500) # High-volume APIs
311
+
312
+ # Apply configuration
313
+ @app.get("/heroes/")
314
+ def read_heroes(session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
315
+ fsp.apply_config(config)
316
+ return fsp.generate_response(select(Hero), session)
317
+ ```
318
+
319
+ ### Strict Mode
320
+
321
+ When `strict_mode=True`, FSPManager raises HTTP 400 errors for unknown filter/sort fields:
322
+
323
+ ```python
324
+ # With strict_mode=True, this raises HTTP 400:
325
+ # GET /heroes/?field=unknown_field&operator=eq&value=test
326
+ # Error: "Unknown field 'unknown_field'. Available fields: age, id, name, secret_name"
327
+ ```
328
+
329
+ ## Convenience Methods
330
+
331
+ ### from_model()
332
+
333
+ Simplify common queries with `from_model()`:
334
+
335
+ ```python
336
+ @app.get("/heroes/")
337
+ def read_heroes(session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
338
+ # Instead of:
339
+ # query = select(Hero)
340
+ # return fsp.generate_response(query, session)
341
+
342
+ # Use:
343
+ return fsp.from_model(Hero, session)
344
+
345
+ # Async version
346
+ @app.get("/heroes/")
347
+ async def read_heroes(session: AsyncSession = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
348
+ return await fsp.from_model_async(Hero, session)
349
+ ```
350
+
351
+ ### Method Chaining
352
+
353
+ Chain configuration methods:
354
+
355
+ ```python
356
+ @app.get("/heroes/")
357
+ def read_heroes(session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
358
+ return (
359
+ fsp
360
+ .with_filters(CommonFilters.active())
361
+ .apply_config(FSPPresets.strict())
362
+ .generate_response(select(Hero), session)
363
+ )
364
+ ```
365
+
208
366
  ## Response model
209
367
 
210
368
  ```
@@ -0,0 +1,13 @@
1
+ fastapi_fsp/__init__.py,sha256=ncmpcclGRIGQsBkRHN-kGDfSgLFZE4_1f0PotzZ83ys,1218
2
+ fastapi_fsp/builder.py,sha256=due8kTVApNXWZJGVDhqe6Ch9jCqdjnNe0rvWNUOgVMw,9660
3
+ fastapi_fsp/config.py,sha256=GcrSyv_wOvNgLFq00fhsZznZH4Fnl0dCBJa73n2QwIs,4977
4
+ fastapi_fsp/filters.py,sha256=zVGPur83pNrDLgIWF2JdYdLHdHGPH-WpZcJNKjkG6Xs,11796
5
+ fastapi_fsp/fsp.py,sha256=ZoTVzrpzvTNQ02hoFbERhYwqFr4rQ6nGcsBcsMlicJo,18440
6
+ fastapi_fsp/models.py,sha256=1MwLBQFmUP8OwO3Gqby1u7s9ruimCR2XGfUzAqF4Tj4,2034
7
+ fastapi_fsp/pagination.py,sha256=hSro5BiyEVl_ZX2kw5-dSj4hiCh_5J7y2i4T3zNOJG0,10544
8
+ fastapi_fsp/presets.py,sha256=hpfUmCaeqoCeb1PimpUoGEW5S0Ycc-yBEZQq6vJWv50,8500
9
+ fastapi_fsp/sorting.py,sha256=rd6lQV3gz_XQ1bokklRjL8AKGiZ3KV7XIu9PyYNbde0,2125
10
+ fastapi_fsp-0.4.0.dist-info/METADATA,sha256=u-qSLGRucBbjEk1cVbu0r91hr-4zl86tSvzgaiYU6fI,12610
11
+ fastapi_fsp-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ fastapi_fsp-0.4.0.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
13
+ fastapi_fsp-0.4.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- fastapi_fsp/__init__.py,sha256=0I_XN_ptNu1NyNRpjdyVm6nwvrilAB8FyT2EfgVF_QA,215
2
- fastapi_fsp/fsp.py,sha256=b6OuHgbTpWrFtxGKm7fcPpjHtD3Cdjrs22m3IYOBhp4,23986
3
- fastapi_fsp/models.py,sha256=1MwLBQFmUP8OwO3Gqby1u7s9ruimCR2XGfUzAqF4Tj4,2034
4
- fastapi_fsp-0.2.3.dist-info/METADATA,sha256=w4wfXs3hye-UI0-h2a81OjkG3yzzZWUq91n1HhmyUi4,8224
5
- fastapi_fsp-0.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
- fastapi_fsp-0.2.3.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
7
- fastapi_fsp-0.2.3.dist-info/RECORD,,