pypaginate 0.2.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 (291) hide show
  1. pypaginate-0.2.0/.gitignore +95 -0
  2. pypaginate-0.2.0/CHANGELOG.md +148 -0
  3. pypaginate-0.2.0/LICENSE +21 -0
  4. pypaginate-0.2.0/PKG-INFO +416 -0
  5. pypaginate-0.2.0/README.md +368 -0
  6. pypaginate-0.2.0/docs/ARCHITECTURE.md +301 -0
  7. pypaginate-0.2.0/docs/CODE_OF_CONDUCT.md +65 -0
  8. pypaginate-0.2.0/docs/FEATURE_GAP_ANALYSIS.md +527 -0
  9. pypaginate-0.2.0/docs/OPTIMIZATION_AUDIT.md +516 -0
  10. pypaginate-0.2.0/docs/README.md +71 -0
  11. pypaginate-0.2.0/docs/TESTING.md +318 -0
  12. pypaginate-0.2.0/docs/_static/custom.css +122 -0
  13. pypaginate-0.2.0/docs/_static/custom.js +33 -0
  14. pypaginate-0.2.0/docs/_static/favicon.svg +21 -0
  15. pypaginate-0.2.0/docs/_static/logo.svg +23 -0
  16. pypaginate-0.2.0/docs/_templates/versions.html +66 -0
  17. pypaginate-0.2.0/docs/api/overview.md +265 -0
  18. pypaginate-0.2.0/docs/api/pypaginate/adapters/fastapi/dependencies/index.rst +52 -0
  19. pypaginate-0.2.0/docs/api/pypaginate/adapters/fastapi/filters/index.rst +76 -0
  20. pypaginate-0.2.0/docs/api/pypaginate/adapters/fastapi/index.rst +153 -0
  21. pypaginate-0.2.0/docs/api/pypaginate/adapters/fastapi/search/index.rst +52 -0
  22. pypaginate-0.2.0/docs/api/pypaginate/adapters/fastapi/sorting/index.rst +52 -0
  23. pypaginate-0.2.0/docs/api/pypaginate/adapters/index.rst +22 -0
  24. pypaginate-0.2.0/docs/api/pypaginate/adapters/memory/backend/index.rst +62 -0
  25. pypaginate-0.2.0/docs/api/pypaginate/adapters/memory/filters/index.rst +42 -0
  26. pypaginate-0.2.0/docs/api/pypaginate/adapters/memory/index.rst +126 -0
  27. pypaginate-0.2.0/docs/api/pypaginate/adapters/memory/search/index.rst +44 -0
  28. pypaginate-0.2.0/docs/api/pypaginate/adapters/memory/sorting/index.rst +44 -0
  29. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/backend/index.rst +78 -0
  30. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/columns/index.rst +40 -0
  31. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/cursor/index.rst +79 -0
  32. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/cursor_codec/index.rst +46 -0
  33. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/filters/index.rst +43 -0
  34. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/index.rst +208 -0
  35. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/keyset/index.rst +87 -0
  36. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/search/index.rst +46 -0
  37. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/sorting/index.rst +44 -0
  38. pypaginate-0.2.0/docs/api/pypaginate/adapters/sqlalchemy/types/index.rst +27 -0
  39. pypaginate-0.2.0/docs/api/pypaginate/domain/enums/index.rst +78 -0
  40. pypaginate-0.2.0/docs/api/pypaginate/domain/exceptions/index.rst +96 -0
  41. pypaginate-0.2.0/docs/api/pypaginate/domain/fast_pages/index.rst +80 -0
  42. pypaginate-0.2.0/docs/api/pypaginate/domain/index.rst +26 -0
  43. pypaginate-0.2.0/docs/api/pypaginate/domain/pages/index.rst +93 -0
  44. pypaginate-0.2.0/docs/api/pypaginate/domain/params/index.rst +96 -0
  45. pypaginate-0.2.0/docs/api/pypaginate/domain/protocols/index.rst +137 -0
  46. pypaginate-0.2.0/docs/api/pypaginate/domain/specs/index.rst +143 -0
  47. pypaginate-0.2.0/docs/api/pypaginate/engine/cursor/index.rst +45 -0
  48. pypaginate-0.2.0/docs/api/pypaginate/engine/index.rst +22 -0
  49. pypaginate-0.2.0/docs/api/pypaginate/engine/paginator/index.rst +65 -0
  50. pypaginate-0.2.0/docs/api/pypaginate/engine/pipeline/index.rst +72 -0
  51. pypaginate-0.2.0/docs/api/pypaginate/filtering/accessor/index.rst +45 -0
  52. pypaginate-0.2.0/docs/api/pypaginate/filtering/engine/index.rst +43 -0
  53. pypaginate-0.2.0/docs/api/pypaginate/filtering/index.rst +96 -0
  54. pypaginate-0.2.0/docs/api/pypaginate/filtering/like/index.rst +53 -0
  55. pypaginate-0.2.0/docs/api/pypaginate/filtering/operators/index.rst +195 -0
  56. pypaginate-0.2.0/docs/api/pypaginate/filtering/regex/index.rst +36 -0
  57. pypaginate-0.2.0/docs/api/pypaginate/filtering/registry/index.rst +66 -0
  58. pypaginate-0.2.0/docs/api/pypaginate/index.rst +401 -0
  59. pypaginate-0.2.0/docs/api/pypaginate/search/engine/index.rst +36 -0
  60. pypaginate-0.2.0/docs/api/pypaginate/search/index.rst +44 -0
  61. pypaginate-0.2.0/docs/api/pypaginate/search/matching/index.rst +46 -0
  62. pypaginate-0.2.0/docs/api/pypaginate/search/parser/index.rst +53 -0
  63. pypaginate-0.2.0/docs/api/pypaginate/sorting/engine/index.rst +47 -0
  64. pypaginate-0.2.0/docs/api/pypaginate/sorting/index.rst +53 -0
  65. pypaginate-0.2.0/docs/api/pypaginate/sorting/keys/index.rst +41 -0
  66. pypaginate-0.2.0/docs/api/pypaginate/text/index.rst +20 -0
  67. pypaginate-0.2.0/docs/api/pypaginate/text/normalize/index.rst +50 -0
  68. pypaginate-0.2.0/docs/comparison.md +199 -0
  69. pypaginate-0.2.0/docs/concepts/architecture.md +320 -0
  70. pypaginate-0.2.0/docs/concepts/cursor-encoding.md +145 -0
  71. pypaginate-0.2.0/docs/concepts/filter-expressions.md +249 -0
  72. pypaginate-0.2.0/docs/concepts/index.md +107 -0
  73. pypaginate-0.2.0/docs/concepts/pagination-strategies.md +205 -0
  74. pypaginate-0.2.0/docs/concepts/search-relevance.md +239 -0
  75. pypaginate-0.2.0/docs/conf.py +423 -0
  76. pypaginate-0.2.0/docs/contributing/architecture.md +190 -0
  77. pypaginate-0.2.0/docs/contributing/code-style.md +270 -0
  78. pypaginate-0.2.0/docs/contributing/development.md +213 -0
  79. pypaginate-0.2.0/docs/contributing/index.md +130 -0
  80. pypaginate-0.2.0/docs/contributing/roadmap.md +158 -0
  81. pypaginate-0.2.0/docs/contributing/testing.md +220 -0
  82. pypaginate-0.2.0/docs/examples/basic-pagination.md +228 -0
  83. pypaginate-0.2.0/docs/examples/fastapi.md +315 -0
  84. pypaginate-0.2.0/docs/examples/filtering.md +181 -0
  85. pypaginate-0.2.0/docs/examples/index.md +54 -0
  86. pypaginate-0.2.0/docs/examples/keyset.md +254 -0
  87. pypaginate-0.2.0/docs/filtering/basic.md +242 -0
  88. pypaginate-0.2.0/docs/filtering/index.md +98 -0
  89. pypaginate-0.2.0/docs/filtering/json-logic.md +205 -0
  90. pypaginate-0.2.0/docs/filtering/operators.md +264 -0
  91. pypaginate-0.2.0/docs/getting-started/first-steps.md +160 -0
  92. pypaginate-0.2.0/docs/getting-started/index.md +64 -0
  93. pypaginate-0.2.0/docs/getting-started/installation.md +231 -0
  94. pypaginate-0.2.0/docs/getting-started/quickstart.md +118 -0
  95. pypaginate-0.2.0/docs/index.md +315 -0
  96. pypaginate-0.2.0/docs/integrations/fastapi.md +371 -0
  97. pypaginate-0.2.0/docs/integrations/index.md +90 -0
  98. pypaginate-0.2.0/docs/integrations/sqlalchemy.md +392 -0
  99. pypaginate-0.2.0/docs/pagination/index.md +164 -0
  100. pypaginate-0.2.0/docs/pagination/keyset.md +243 -0
  101. pypaginate-0.2.0/docs/pagination/memory.md +204 -0
  102. pypaginate-0.2.0/docs/pagination/offset.md +281 -0
  103. pypaginate-0.2.0/docs/poly.py +276 -0
  104. pypaginate-0.2.0/docs/search/fuzzy.md +273 -0
  105. pypaginate-0.2.0/docs/search/index.md +104 -0
  106. pypaginate-0.2.0/docs/search/text-search.md +238 -0
  107. pypaginate-0.2.0/docs/sorting/basic.md +186 -0
  108. pypaginate-0.2.0/docs/sorting/index.md +93 -0
  109. pypaginate-0.2.0/docs/sorting/multi-column.md +228 -0
  110. pypaginate-0.2.0/examples/README.md +56 -0
  111. pypaginate-0.2.0/examples/basic_pagination.py +46 -0
  112. pypaginate-0.2.0/examples/fastapi_integration.py +113 -0
  113. pypaginate-0.2.0/examples/filtering.py +67 -0
  114. pypaginate-0.2.0/examples/keyset_pagination.py +99 -0
  115. pypaginate-0.2.0/pyproject.toml +504 -0
  116. pypaginate-0.2.0/src/pypaginate/__init__.py +71 -0
  117. pypaginate-0.2.0/src/pypaginate/_cli/__init__.py +96 -0
  118. pypaginate-0.2.0/src/pypaginate/_cli/build.py +96 -0
  119. pypaginate-0.2.0/src/pypaginate/_cli/commands.py +171 -0
  120. pypaginate-0.2.0/src/pypaginate/_cli/output.py +91 -0
  121. pypaginate-0.2.0/src/pypaginate/_cli/runner.py +50 -0
  122. pypaginate-0.2.0/src/pypaginate/_detection.py +51 -0
  123. pypaginate-0.2.0/src/pypaginate/_dispatch.py +208 -0
  124. pypaginate-0.2.0/src/pypaginate/adapters/__init__.py +3 -0
  125. pypaginate-0.2.0/src/pypaginate/adapters/fastapi/__init__.py +25 -0
  126. pypaginate-0.2.0/src/pypaginate/adapters/fastapi/dependencies.py +67 -0
  127. pypaginate-0.2.0/src/pypaginate/adapters/fastapi/filters.py +75 -0
  128. pypaginate-0.2.0/src/pypaginate/adapters/fastapi/search.py +41 -0
  129. pypaginate-0.2.0/src/pypaginate/adapters/fastapi/sorting.py +48 -0
  130. pypaginate-0.2.0/src/pypaginate/adapters/memory/__init__.py +16 -0
  131. pypaginate-0.2.0/src/pypaginate/adapters/memory/backend.py +69 -0
  132. pypaginate-0.2.0/src/pypaginate/adapters/memory/filters.py +118 -0
  133. pypaginate-0.2.0/src/pypaginate/adapters/memory/search.py +171 -0
  134. pypaginate-0.2.0/src/pypaginate/adapters/memory/sorting.py +84 -0
  135. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/__init__.py +26 -0
  136. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/backend.py +144 -0
  137. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/columns.py +85 -0
  138. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/cursor.py +305 -0
  139. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/filters.py +192 -0
  140. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/keyset.py +187 -0
  141. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/search.py +132 -0
  142. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/sorting.py +94 -0
  143. pypaginate-0.2.0/src/pypaginate/adapters/sqlalchemy/types.py +21 -0
  144. pypaginate-0.2.0/src/pypaginate/domain/__init__.py +3 -0
  145. pypaginate-0.2.0/src/pypaginate/domain/enums.py +63 -0
  146. pypaginate-0.2.0/src/pypaginate/domain/exceptions.py +102 -0
  147. pypaginate-0.2.0/src/pypaginate/domain/fast_pages.py +93 -0
  148. pypaginate-0.2.0/src/pypaginate/domain/pages.py +152 -0
  149. pypaginate-0.2.0/src/pypaginate/domain/params.py +114 -0
  150. pypaginate-0.2.0/src/pypaginate/domain/protocols.py +120 -0
  151. pypaginate-0.2.0/src/pypaginate/domain/specs.py +171 -0
  152. pypaginate-0.2.0/src/pypaginate/engine/__init__.py +3 -0
  153. pypaginate-0.2.0/src/pypaginate/engine/cursor.py +54 -0
  154. pypaginate-0.2.0/src/pypaginate/engine/cursor_codec.py +108 -0
  155. pypaginate-0.2.0/src/pypaginate/engine/paginator.py +109 -0
  156. pypaginate-0.2.0/src/pypaginate/engine/pipeline.py +167 -0
  157. pypaginate-0.2.0/src/pypaginate/filtering/__init__.py +9 -0
  158. pypaginate-0.2.0/src/pypaginate/filtering/accessor.py +130 -0
  159. pypaginate-0.2.0/src/pypaginate/filtering/engine.py +208 -0
  160. pypaginate-0.2.0/src/pypaginate/filtering/like.py +70 -0
  161. pypaginate-0.2.0/src/pypaginate/filtering/operators.py +228 -0
  162. pypaginate-0.2.0/src/pypaginate/filtering/regex.py +44 -0
  163. pypaginate-0.2.0/src/pypaginate/filtering/registry.py +112 -0
  164. pypaginate-0.2.0/src/pypaginate/py.typed +0 -0
  165. pypaginate-0.2.0/src/pypaginate/search/__init__.py +8 -0
  166. pypaginate-0.2.0/src/pypaginate/search/engine.py +217 -0
  167. pypaginate-0.2.0/src/pypaginate/search/matching.py +78 -0
  168. pypaginate-0.2.0/src/pypaginate/search/parser.py +64 -0
  169. pypaginate-0.2.0/src/pypaginate/sorting/__init__.py +8 -0
  170. pypaginate-0.2.0/src/pypaginate/sorting/engine.py +78 -0
  171. pypaginate-0.2.0/src/pypaginate/sorting/keys.py +70 -0
  172. pypaginate-0.2.0/src/pypaginate/text/__init__.py +3 -0
  173. pypaginate-0.2.0/src/pypaginate/text/normalize.py +64 -0
  174. pypaginate-0.2.0/tests/__init__.py +1 -0
  175. pypaginate-0.2.0/tests/architecture/__init__.py +0 -0
  176. pypaginate-0.2.0/tests/architecture/test_file_limits.py +61 -0
  177. pypaginate-0.2.0/tests/architecture/test_imports.py +86 -0
  178. pypaginate-0.2.0/tests/architecture/test_protocols.py +56 -0
  179. pypaginate-0.2.0/tests/conftest.py +171 -0
  180. pypaginate-0.2.0/tests/e2e/__init__.py +3 -0
  181. pypaginate-0.2.0/tests/e2e/conftest.py +6 -0
  182. pypaginate-0.2.0/tests/e2e/test_combined_flows.py +76 -0
  183. pypaginate-0.2.0/tests/e2e/test_completeness.py +63 -0
  184. pypaginate-0.2.0/tests/e2e/test_fastapi_flows.py +382 -0
  185. pypaginate-0.2.0/tests/e2e/test_filter_flows.py +72 -0
  186. pypaginate-0.2.0/tests/e2e/test_offset_flows.py +66 -0
  187. pypaginate-0.2.0/tests/e2e/test_sort_flows.py +55 -0
  188. pypaginate-0.2.0/tests/factories/__init__.py +32 -0
  189. pypaginate-0.2.0/tests/factories/data.py +56 -0
  190. pypaginate-0.2.0/tests/factories/domain.py +115 -0
  191. pypaginate-0.2.0/tests/fixtures/__init__.py +9 -0
  192. pypaginate-0.2.0/tests/fixtures/backends.py +326 -0
  193. pypaginate-0.2.0/tests/fixtures/data.py +115 -0
  194. pypaginate-0.2.0/tests/fixtures/database.py +104 -0
  195. pypaginate-0.2.0/tests/fixtures/helpers.py +30 -0
  196. pypaginate-0.2.0/tests/fixtures/models.py +107 -0
  197. pypaginate-0.2.0/tests/integration/__init__.py +3 -0
  198. pypaginate-0.2.0/tests/integration/conftest.py +6 -0
  199. pypaginate-0.2.0/tests/integration/test_custom_backend.py +64 -0
  200. pypaginate-0.2.0/tests/integration/test_fastapi.py +471 -0
  201. pypaginate-0.2.0/tests/integration/test_filter_groups.py +113 -0
  202. pypaginate-0.2.0/tests/integration/test_filtering.py +111 -0
  203. pypaginate-0.2.0/tests/integration/test_pagination.py +94 -0
  204. pypaginate-0.2.0/tests/integration/test_pipeline.py +58 -0
  205. pypaginate-0.2.0/tests/integration/test_postgresql.py +184 -0
  206. pypaginate-0.2.0/tests/integration/test_search.py +52 -0
  207. pypaginate-0.2.0/tests/integration/test_smoke.py +33 -0
  208. pypaginate-0.2.0/tests/integration/test_sorting.py +83 -0
  209. pypaginate-0.2.0/tests/perf/__init__.py +1 -0
  210. pypaginate-0.2.0/tests/perf/conftest.py +257 -0
  211. pypaginate-0.2.0/tests/perf/test_boundary.py +179 -0
  212. pypaginate-0.2.0/tests/perf/test_comparison.py +197 -0
  213. pypaginate-0.2.0/tests/perf/test_competitor_scaling.py +206 -0
  214. pypaginate-0.2.0/tests/perf/test_competitor_scaling_sa.py +599 -0
  215. pypaginate-0.2.0/tests/perf/test_competitors.py +635 -0
  216. pypaginate-0.2.0/tests/perf/test_error_handling.py +323 -0
  217. pypaginate-0.2.0/tests/perf/test_fastapi_perf.py +1124 -0
  218. pypaginate-0.2.0/tests/perf/test_fastapi_scaling.py +449 -0
  219. pypaginate-0.2.0/tests/perf/test_filtering.py +168 -0
  220. pypaginate-0.2.0/tests/perf/test_overhead.py +383 -0
  221. pypaginate-0.2.0/tests/perf/test_pagination.py +199 -0
  222. pypaginate-0.2.0/tests/perf/test_pipeline.py +215 -0
  223. pypaginate-0.2.0/tests/perf/test_scaling.py +310 -0
  224. pypaginate-0.2.0/tests/perf/test_search.py +142 -0
  225. pypaginate-0.2.0/tests/perf/test_serialization.py +391 -0
  226. pypaginate-0.2.0/tests/perf/test_sorting.py +143 -0
  227. pypaginate-0.2.0/tests/property/__init__.py +1 -0
  228. pypaginate-0.2.0/tests/property/conftest.py +53 -0
  229. pypaginate-0.2.0/tests/property/strategies.py +80 -0
  230. pypaginate-0.2.0/tests/property/test_filtering.py +72 -0
  231. pypaginate-0.2.0/tests/property/test_pagination.py +94 -0
  232. pypaginate-0.2.0/tests/property/test_search.py +92 -0
  233. pypaginate-0.2.0/tests/property/test_sorting.py +78 -0
  234. pypaginate-0.2.0/tests/unit/__init__.py +0 -0
  235. pypaginate-0.2.0/tests/unit/adapters/__init__.py +0 -0
  236. pypaginate-0.2.0/tests/unit/adapters/fastapi/__init__.py +0 -0
  237. pypaginate-0.2.0/tests/unit/adapters/fastapi/test_dependencies.py +200 -0
  238. pypaginate-0.2.0/tests/unit/adapters/fastapi/test_filter_dep.py +71 -0
  239. pypaginate-0.2.0/tests/unit/adapters/fastapi/test_search_dep.py +38 -0
  240. pypaginate-0.2.0/tests/unit/adapters/fastapi/test_sort_dep.py +50 -0
  241. pypaginate-0.2.0/tests/unit/adapters/memory/__init__.py +0 -0
  242. pypaginate-0.2.0/tests/unit/adapters/memory/test_backend.py +92 -0
  243. pypaginate-0.2.0/tests/unit/adapters/memory/test_filters.py +84 -0
  244. pypaginate-0.2.0/tests/unit/adapters/memory/test_search.py +111 -0
  245. pypaginate-0.2.0/tests/unit/adapters/memory/test_search_advanced.py +74 -0
  246. pypaginate-0.2.0/tests/unit/adapters/memory/test_sorting.py +63 -0
  247. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/__init__.py +0 -0
  248. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/conftest.py +115 -0
  249. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_backend.py +136 -0
  250. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_columns.py +51 -0
  251. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_cursor.py +233 -0
  252. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_cursor_codec.py +121 -0
  253. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_filters.py +222 -0
  254. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_filters_db.py +106 -0
  255. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_keyset.py +196 -0
  256. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_search.py +216 -0
  257. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_sorting.py +179 -0
  258. pypaginate-0.2.0/tests/unit/adapters/sqlalchemy/test_types.py +15 -0
  259. pypaginate-0.2.0/tests/unit/conftest.py +43 -0
  260. pypaginate-0.2.0/tests/unit/domain/__init__.py +0 -0
  261. pypaginate-0.2.0/tests/unit/domain/test_enums.py +59 -0
  262. pypaginate-0.2.0/tests/unit/domain/test_exceptions.py +114 -0
  263. pypaginate-0.2.0/tests/unit/domain/test_fast_pages.py +306 -0
  264. pypaginate-0.2.0/tests/unit/domain/test_models.py +86 -0
  265. pypaginate-0.2.0/tests/unit/domain/test_pages.py +203 -0
  266. pypaginate-0.2.0/tests/unit/domain/test_params.py +146 -0
  267. pypaginate-0.2.0/tests/unit/domain/test_protocols.py +124 -0
  268. pypaginate-0.2.0/tests/unit/domain/test_specs.py +136 -0
  269. pypaginate-0.2.0/tests/unit/engine/__init__.py +0 -0
  270. pypaginate-0.2.0/tests/unit/engine/test_cursor.py +144 -0
  271. pypaginate-0.2.0/tests/unit/engine/test_dispatch.py +213 -0
  272. pypaginate-0.2.0/tests/unit/engine/test_paginator.py +302 -0
  273. pypaginate-0.2.0/tests/unit/engine/test_pipeline.py +285 -0
  274. pypaginate-0.2.0/tests/unit/filtering/__init__.py +0 -0
  275. pypaginate-0.2.0/tests/unit/filtering/test_accessor.py +105 -0
  276. pypaginate-0.2.0/tests/unit/filtering/test_engine.py +193 -0
  277. pypaginate-0.2.0/tests/unit/filtering/test_engine_like.py +179 -0
  278. pypaginate-0.2.0/tests/unit/filtering/test_groups.py +151 -0
  279. pypaginate-0.2.0/tests/unit/filtering/test_like.py +179 -0
  280. pypaginate-0.2.0/tests/unit/filtering/test_operators.py +212 -0
  281. pypaginate-0.2.0/tests/unit/filtering/test_registry.py +93 -0
  282. pypaginate-0.2.0/tests/unit/search/__init__.py +0 -0
  283. pypaginate-0.2.0/tests/unit/search/test_engine.py +187 -0
  284. pypaginate-0.2.0/tests/unit/search/test_matching.py +137 -0
  285. pypaginate-0.2.0/tests/unit/search/test_parser.py +98 -0
  286. pypaginate-0.2.0/tests/unit/sorting/__init__.py +0 -0
  287. pypaginate-0.2.0/tests/unit/sorting/test_engine.py +185 -0
  288. pypaginate-0.2.0/tests/unit/sorting/test_keys.py +90 -0
  289. pypaginate-0.2.0/tests/unit/test_public_api.py +17 -0
  290. pypaginate-0.2.0/tests/unit/text/__init__.py +0 -0
  291. pypaginate-0.2.0/tests/unit/text/test_normalize.py +88 -0
@@ -0,0 +1,95 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+
28
+ # UV
29
+ .uv/
30
+
31
+ # IDE
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+ *~
37
+ .project
38
+ .pydevproject
39
+ .settings/
40
+
41
+ # Testing
42
+ .pytest_cache/
43
+ .coverage
44
+ .coverage.*
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+
53
+ # Type checking
54
+ .mypy_cache/
55
+ .dmypy.json
56
+ dmypy.json
57
+
58
+ # Linting
59
+ .ruff_cache/
60
+
61
+ # Documentation
62
+ docs/_build/
63
+ docs-sphinx/_build/
64
+ site/
65
+ v1docs/
66
+
67
+ # Jupyter
68
+ .ipynb_checkpoints/
69
+
70
+ # OS
71
+ .DS_Store
72
+ Thumbs.db
73
+
74
+ # Local configuration
75
+ .env
76
+ .env.local
77
+ *.local
78
+
79
+ # Node.js (MCP servers)
80
+ node_modules/
81
+
82
+ # Build artifacts
83
+ *.manifest
84
+ *.spec
85
+
86
+ # Session artifacts (AI-generated planning docs)
87
+ REFACTORING_PLAN.md
88
+ REFACTORING_ROADMAP.md
89
+ AUDIT_IMPLEMENTATION_PLAN.md
90
+ AUDIT_R2_IMPLEMENTATION_PLAN.md
91
+ DOCS_REFACTOR_PLAN.md
92
+
93
+ # Installer logs
94
+ pip-log.txt
95
+ pip-delete-this-directory.txt
@@ -0,0 +1,148 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Planned for v0.3.0
11
+ - JSON Logic dict-to-FilterGroup parser for frontend integration
12
+ - Django `__` filter format parser
13
+ - `add_pagination(app)` zero-config FastAPI middleware
14
+ - HATEOAS link generation
15
+ - Additional ORM support (Beanie, Tortoise)
16
+
17
+ ---
18
+
19
+ ## [0.2.0] - 2025-XX-XX
20
+
21
+ ### Added
22
+
23
+ #### Architecture
24
+ - Hexagonal architecture with domain/engine/adapter layers
25
+ - Protocol-based backends (`PaginationBackend`, `CursorBackend`, `FilterBackend`, `SortBackend`, `SearchBackend`)
26
+ - Universal `paginate()` entry point with Elysia-style type inference
27
+ - `SyncPipeline` and `AsyncPipeline` for composing filter + sort + search + paginate
28
+
29
+ #### Pagination
30
+ - `OffsetParams` / `OffsetPage` (replaces `PageParams` / `Page`)
31
+ - `CursorParams` / `CursorPage` for cursor/keyset pagination
32
+ - `OverflowStrategy` (EMPTY or CLAMP) for out-of-range pages
33
+ - Custom count query via `SQLAlchemyBackend(session, count_query=...)`
34
+ - Row deduplication via `SQLAlchemyBackend(session, unique=True)`
35
+ - `SyncSQLAlchemyBackend` and `SyncSQLAlchemyCursorBackend` for sync sessions
36
+ - Fast in-memory path (no backend allocation for list + OffsetParams)
37
+
38
+ #### Filtering
39
+ - 20 operators: eq, ne, gt, gte, lt, lte, in, not_in, contains, starts_with, ends_with, like, ilike, between, is_null, is_not_null, regex, empty, not_empty, exists
40
+ - `FilterGroup` with `And()` / `Or()` builders for nested boolean groups (up to 5 levels)
41
+ - Compiled predicate closures (compile once, evaluate N times)
42
+ - `SQLAlchemyFilterBackend` for SQL WHERE clause generation
43
+ - `OperatorRegistry` for extensible operator lookup
44
+
45
+ #### Search
46
+ - `SearchSpec` with `weights`, `fuzzy`, `threshold`, `min_length`, `max_results`
47
+ - `FuzzyMode.EXACT`, `FuzzyMode.FUZZY` (partial_ratio), `FuzzyMode.TOKEN_SORT` (token_sort_ratio)
48
+ - `SearchFieldMode.EXACT`, `PREFIX`, `CONTAINS`
49
+ - Unicode normalization with accent removal
50
+ - `SQLAlchemySearchBackend` for SQL LIKE/ILIKE search
51
+
52
+ #### Sorting
53
+ - `SortSpec` with `direction` and `nulls` (NullsPosition.FIRST / LAST)
54
+ - Multi-key stable sorting
55
+ - `SQLAlchemySortBackend` for SQL ORDER BY
56
+
57
+ #### FastAPI Integration
58
+ - `OffsetDep`, `CursorDep` (Annotated dependency types)
59
+ - `FilterDep` with `FilterField()` for declarative filters
60
+ - `SortDep` for `?sort=name,-age` query parsing
61
+ - `SearchDep` for `?q=alice&search_fields=name,email` query parsing
62
+
63
+ #### Performance
64
+ - msgspec fast page construction (`pypaginate[fast]`)
65
+ - Compiled field accessors, pre-normalized search tokens
66
+ - `__slots__` on all stateful classes
67
+ - Optional google-re2 for ReDoS safety (`pypaginate[security]`)
68
+ - LIKE pattern string method dispatch (bypasses fnmatch/regex for common patterns)
69
+
70
+ ### Changed
71
+ - Renamed `PageParams` to `OffsetParams`, `Page` to `OffsetPage`
72
+ - Replaced `paginate_entities()` / `paginate_rows()` with universal `paginate()`
73
+ - Replaced JSON Logic dict filters with typed `FilterSpec` / `FilterGroup`
74
+ - Replaced `SearchOptions` with `SearchSpec`
75
+ - Moved `FilterEngine` to `pypaginate.filtering.engine`
76
+ - Moved `SortEngine` to `pypaginate.sorting.engine`
77
+ - FastAPI deps use `Annotated` types instead of function-based `Depends`
78
+
79
+ ### Removed
80
+ - `SqlPaginator`, `MemoryPaginator` (replaced by protocol backends)
81
+ - `PaginationSnapshot` (direct page return)
82
+ - `MemorySearchService`, `SqlSearchService` (replaced by `SearchEngine` + backends)
83
+ - `SqlSortAdapter`, `SqlFilterAdapter` (replaced by SA backends)
84
+ - `get_pagination_params()`, `PagedResponse` (replaced by `OffsetDep`, `OffsetPage`)
85
+ - JSON Logic dict format (replaced by typed `FilterGroup`)
86
+ - JMESPath array access (replaced by dot notation)
87
+
88
+ ---
89
+
90
+ ## [0.1.0] - 2025-01-30
91
+
92
+ ### Added
93
+
94
+ #### Core Pagination
95
+ - `Page[T]` generic response model with metadata (total, page, limit, pages)
96
+ - `PageParams` dataclass for pagination parameters
97
+ - Offset-based pagination with configurable page size
98
+ - Keyset (cursor-based) pagination for large datasets using `sqlakeyset`
99
+
100
+ #### Pagination Engines
101
+ - `SqlPaginator` - SQLAlchemy-based pagination engine
102
+ - `MemoryPaginator` - In-memory pagination for Python collections
103
+ - `paginate_entities()` - High-level async pagination API
104
+
105
+ #### Filtering
106
+ - `FilterEngine` with JSON Logic support for complex queries
107
+ - Predicate-based filtering system
108
+ - Support for operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `not_in`, `like`, `ilike`, `is_null`, `startswith`, `endswith`
109
+ - Logical operators: `and`, `or`, `not`
110
+
111
+ #### Search
112
+ - `SqlSearchService` for full-text search
113
+ - Fuzzy matching with RapidFuzz integration
114
+ - Configurable similarity thresholds
115
+ - Accent-insensitive search option
116
+ - Multi-field search support
117
+
118
+ #### Sorting
119
+ - `SortEngine` for sort operations
120
+ - `SqlSortAdapter` for SQLAlchemy integration
121
+ - Multi-column sorting support
122
+ - Ascending/descending order
123
+
124
+ #### FastAPI Integration
125
+ - `get_pagination_params()` dependency for FastAPI
126
+ - `PagedResponse` Pydantic model for OpenAPI documentation
127
+ - Type-safe parameter extraction from query strings
128
+
129
+ #### Developer Experience
130
+ - Full type hints with mypy --strict compatibility
131
+ - Comprehensive docstrings
132
+ - Async/await support throughout
133
+
134
+ ### Technical Details
135
+ - Python 3.11+ required
136
+ - SQLAlchemy 2.0+ for database operations
137
+ - Pydantic v2 for data validation
138
+ - Optional RapidFuzz for fuzzy search
139
+
140
+ ---
141
+
142
+ ## Future Releases
143
+
144
+ See the [Roadmap](https://pypaginate.readthedocs.io/contributing/roadmap/) for detailed planning of future versions.
145
+
146
+ [Unreleased]: https://github.com/CybLow/pypaginate/compare/v0.2.0...HEAD
147
+ [0.2.0]: https://github.com/CybLow/pypaginate/compare/v0.1.0...v0.2.0
148
+ [0.1.0]: https://github.com/CybLow/pypaginate/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 pypaginate Contributors
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,416 @@
1
+ Metadata-Version: 2.4
2
+ Name: pypaginate
3
+ Version: 0.2.0
4
+ Summary: Universal pagination toolkit for Python — one function, any backend, auto-detects sync/async
5
+ Project-URL: Homepage, https://github.com/CybLow/pypaginate
6
+ Project-URL: Documentation, https://pypaginate.readthedocs.io
7
+ Project-URL: Repository, https://github.com/CybLow/pypaginate
8
+ Project-URL: Issues, https://github.com/CybLow/pypaginate/issues
9
+ Project-URL: Changelog, https://github.com/CybLow/pypaginate/blob/main/CHANGELOG.md
10
+ Author-email: CybLow <cyber_lolow@protonmail.com>
11
+ Maintainer-email: CybLow <cyber_lolow@protonmail.com>
12
+ License: MIT
13
+ License-File: LICENSE
14
+ Keywords: async,cursor,database,fastapi,filtering,jsonlogic,keyset,orm,pagination,search,sqlalchemy
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Environment :: Web Environment
17
+ Classifier: Framework :: FastAPI
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Database
27
+ Classifier: Topic :: Database :: Front-Ends
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.11
31
+ Requires-Dist: pydantic>=2.0.0
32
+ Provides-Extra: all
33
+ Requires-Dist: fastapi>=0.95.0; extra == 'all'
34
+ Requires-Dist: msgspec>=0.18.0; extra == 'all'
35
+ Requires-Dist: rapidfuzz>=3.0.0; extra == 'all'
36
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0; extra == 'all'
37
+ Provides-Extra: fast
38
+ Requires-Dist: msgspec>=0.18.0; extra == 'fast'
39
+ Provides-Extra: fastapi
40
+ Requires-Dist: fastapi>=0.95.0; extra == 'fastapi'
41
+ Provides-Extra: search
42
+ Requires-Dist: rapidfuzz>=3.0.0; extra == 'search'
43
+ Provides-Extra: security
44
+ Requires-Dist: google-re2>=1.0; extra == 'security'
45
+ Provides-Extra: sqlalchemy
46
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.0; extra == 'sqlalchemy'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # pypaginate
50
+
51
+ **Universal pagination toolkit for Python -- one function, any backend, auto-detects sync/async.**
52
+
53
+ [![CI](https://github.com/CybLow/pypaginate/actions/workflows/ci.yml/badge.svg)](https://github.com/CybLow/pypaginate/actions/workflows/ci.yml)
54
+ [![PyPI version](https://img.shields.io/pypi/v/pypaginate.svg)](https://pypi.org/project/pypaginate/)
55
+ [![Python Versions](https://img.shields.io/pypi/pyversions/pypaginate.svg)](https://pypi.org/project/pypaginate/)
56
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
57
+ [![codecov](https://codecov.io/gh/CybLow/pypaginate/branch/main/graph/badge.svg)](https://codecov.io/gh/CybLow/pypaginate)
58
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
59
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
60
+
61
+ pypaginate provides a single `paginate()` function that works with lists, SQLAlchemy queries (async and sync), and cursor-based pagination. The return type is automatically inferred from the params you pass in.
62
+
63
+ ## Features
64
+
65
+ - **One function** -- `paginate()` handles lists, SQLAlchemy queries, sync and async
66
+ - **Type-safe inference** -- `OffsetParams` returns `OffsetPage`, `CursorParams` returns `CursorPage`
67
+ - **Filtering** -- 20 operators (eq, gte, contains, between, regex, etc.)
68
+ - **Sorting** -- multi-column with direction and null placement control
69
+ - **Search** -- full-text with optional fuzzy matching (RapidFuzz)
70
+ - **FastAPI** -- `Annotated` dependencies for pagination, filtering, sorting, and search
71
+ - **Cursor pagination** -- keyset/cursor-based pagination via sqlakeyset
72
+ - **Pipeline** -- compose filter + sort + search + paginate in one call
73
+ - **100% typed** -- mypy strict mode, Pydantic v2 models
74
+
75
+ ## Installation
76
+
77
+ ```bash
78
+ # Core (in-memory pagination only)
79
+ pip install pypaginate
80
+
81
+ # With SQLAlchemy support
82
+ pip install pypaginate[sqlalchemy]
83
+
84
+ # With FastAPI integration
85
+ pip install pypaginate[fastapi]
86
+
87
+ # With fuzzy search (RapidFuzz)
88
+ pip install pypaginate[search]
89
+
90
+ # Everything
91
+ pip install pypaginate[all]
92
+ ```
93
+
94
+ Or with [uv](https://docs.astral.sh/uv/):
95
+
96
+ ```bash
97
+ uv add pypaginate
98
+ uv add pypaginate[all]
99
+ ```
100
+
101
+ ## Quick Start
102
+
103
+ Paginate a list in 3 lines:
104
+
105
+ ```python
106
+ from pypaginate import paginate, OffsetParams
107
+
108
+ page = paginate([1, 2, 3, 4, 5], OffsetParams(page=1, limit=2))
109
+
110
+ page.items # [1, 2]
111
+ page.total # 5
112
+ page.pages # 3
113
+ page.has_next # True
114
+ ```
115
+
116
+ ## SQLAlchemy (Async)
117
+
118
+ ```python
119
+ from sqlalchemy import select
120
+ from sqlalchemy.ext.asyncio import AsyncSession
121
+ from pypaginate import paginate, OffsetParams
122
+ from pypaginate.adapters.sqlalchemy import SQLAlchemyBackend
123
+
124
+ async def list_users(session: AsyncSession):
125
+ stmt = select(User).order_by(User.created_at.desc())
126
+ backend = SQLAlchemyBackend(session)
127
+
128
+ page = await paginate(stmt, OffsetParams(page=1, limit=20), backend=backend)
129
+
130
+ page.items # list[User]
131
+ page.total # int
132
+ page.has_next # bool
133
+ ```
134
+
135
+ For sync sessions, use `SyncSQLAlchemyBackend`:
136
+
137
+ ```python
138
+ from sqlalchemy.orm import Session
139
+ from pypaginate.adapters.sqlalchemy import SyncSQLAlchemyBackend
140
+
141
+ def list_users(session: Session):
142
+ backend = SyncSQLAlchemyBackend(session)
143
+ page = paginate(select(User), OffsetParams(page=1, limit=20), backend=backend)
144
+ ```
145
+
146
+ ## Cursor Pagination
147
+
148
+ For large datasets where offset-based pagination is inefficient:
149
+
150
+ ```python
151
+ from pypaginate import paginate, CursorParams
152
+ from pypaginate.adapters.sqlalchemy import SQLAlchemyCursorBackend
153
+
154
+ async def scroll_users(session: AsyncSession, cursor: str | None = None):
155
+ stmt = select(User).order_by(User.id)
156
+ backend = SQLAlchemyCursorBackend(session)
157
+
158
+ page = await paginate(stmt, CursorParams(limit=20, after=cursor), backend=backend)
159
+
160
+ page.items # list[User]
161
+ page.next_cursor # str | None -- pass to next request
162
+ page.previous_cursor # str | None
163
+ page.has_next # bool
164
+ ```
165
+
166
+ ## FastAPI Integration
167
+
168
+ pypaginate provides `Annotated` dependency types for clean FastAPI integration:
169
+
170
+ ```python
171
+ from fastapi import FastAPI
172
+ from pypaginate import paginate, OffsetPage
173
+ from pypaginate.adapters.fastapi import OffsetDep
174
+
175
+ app = FastAPI()
176
+
177
+ @app.get("/users")
178
+ async def list_users(params: OffsetDep) -> OffsetPage[dict]:
179
+ users = [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]
180
+ return paginate(users, params)
181
+ ```
182
+
183
+ Available dependencies:
184
+
185
+ | Dependency | Query Params | Produces |
186
+ |---|---|---|
187
+ | `OffsetDep` | `?page=1&limit=20` | `OffsetParams` |
188
+ | `CursorDep` | `?limit=20&after=abc` | `CursorParams` |
189
+ | `FilterDep` | (user-defined fields) | `list[FilterSpec]` |
190
+ | `SortDep` | `?sort=name,-age` | `list[SortSpec]` |
191
+ | `SearchDep` | `?q=alice&search_fields=name,email` | `SearchSpec` |
192
+
193
+ ### Declarative Filters
194
+
195
+ ```python
196
+ from typing import Annotated
197
+ from fastapi import Query
198
+ from pypaginate.adapters.fastapi import FilterDep, FilterField
199
+
200
+ class UserFilters(FilterDep):
201
+ name: str | None = FilterField(None, operator="contains")
202
+ age_min: int | None = FilterField(None, field="age", operator="gte")
203
+ status: str | None = FilterField(None, operator="eq")
204
+
205
+ @app.get("/users")
206
+ async def list_users(
207
+ params: OffsetDep,
208
+ filters: Annotated[UserFilters, Query()],
209
+ ):
210
+ # filters.to_specs() returns list[FilterSpec] for non-None fields
211
+ ...
212
+ ```
213
+
214
+ ### Sorting and Search
215
+
216
+ ```python
217
+ from pypaginate.adapters.fastapi import OffsetDep, SortDep, SearchDep
218
+
219
+ @app.get("/users")
220
+ async def list_users(params: OffsetDep, sort: SortDep, search: SearchDep):
221
+ # sort: ?sort=name,-created_at (- prefix = descending)
222
+ # search: ?q=alice&search_fields=name,email
223
+ ...
224
+ ```
225
+
226
+ ## Filtering
227
+
228
+ Use `FilterSpec` to define filter conditions:
229
+
230
+ ```python
231
+ from pypaginate import FilterSpec
232
+ from pypaginate.filtering import FilterEngine, create_default_registry
233
+
234
+ engine = FilterEngine(create_default_registry())
235
+
236
+ users = [
237
+ {"name": "Alice", "age": 30, "status": "active"},
238
+ {"name": "Bob", "age": 25, "status": "inactive"},
239
+ {"name": "Charlie", "age": 35, "status": "active"},
240
+ ]
241
+
242
+ # Simple equality
243
+ active = engine.apply(users, [FilterSpec(field="status", value="active")])
244
+ # [Alice, Charlie]
245
+
246
+ # Multiple filters (AND by default)
247
+ result = engine.apply(users, [
248
+ FilterSpec(field="age", operator="gte", value=30),
249
+ FilterSpec(field="status", value="active"),
250
+ ])
251
+ # [Alice, Charlie]
252
+ ```
253
+
254
+ ### Nested Filter Groups
255
+
256
+ ```python
257
+ from pypaginate import And, Or, FilterSpec
258
+
259
+ # (status = active) AND (age >= 30 OR name contains "bob")
260
+ group = And(
261
+ FilterSpec(field="status", value="active"),
262
+ Or(
263
+ FilterSpec(field="age", operator="gte", value=30),
264
+ FilterSpec(field="name", operator="contains", value="bob"),
265
+ ),
266
+ )
267
+
268
+ result = engine.apply(users, group)
269
+ ```
270
+
271
+ ### Available Filter Operators
272
+
273
+ | Operator | Description | Example |
274
+ |---|---|---|
275
+ | `eq`, `ne` | Equality / inequality | `FilterSpec(field="status", value="active")` |
276
+ | `gt`, `gte`, `lt`, `lte` | Comparisons | `FilterSpec(field="age", operator="gte", value=18)` |
277
+ | `in`, `not_in` | Membership | `FilterSpec(field="role", operator="in", value=["admin", "user"])` |
278
+ | `contains`, `starts_with`, `ends_with` | Text matching | `FilterSpec(field="name", operator="contains", value="ali")` |
279
+ | `like`, `ilike` | SQL-style patterns | `FilterSpec(field="email", operator="like", value="%@gmail.com")` |
280
+ | `between` | Range | `FilterSpec(field="price", operator="between", value=[10, 100])` |
281
+ | `is_null`, `is_not_null` | Null checks | `FilterSpec(field="notes", operator="is_null")` |
282
+ | `empty`, `not_empty` | Empty checks | `FilterSpec(field="tags", operator="not_empty")` |
283
+ | `exists` | Field existence | `FilterSpec(field="id", operator="exists")` |
284
+ | `regex` | Regex matching | `FilterSpec(field="code", operator="regex", value="^A\\d+")` |
285
+
286
+ ## Sorting
287
+
288
+ ```python
289
+ from pypaginate import SortSpec, SortDirection
290
+
291
+ from pypaginate.sorting import SortEngine
292
+
293
+ engine = SortEngine()
294
+
295
+ users = [
296
+ {"name": "Charlie", "age": 35},
297
+ {"name": "Alice", "age": 30},
298
+ {"name": "Bob", "age": 25},
299
+ ]
300
+
301
+ sorted_users = engine.apply(users, [
302
+ SortSpec(field="age", direction=SortDirection.DESC),
303
+ ])
304
+ # [Charlie (35), Alice (30), Bob (25)]
305
+ ```
306
+
307
+ ## Search
308
+
309
+ ```python
310
+ from pypaginate import SearchSpec
311
+
312
+ from pypaginate.search import SearchEngine
313
+
314
+ engine = SearchEngine()
315
+
316
+ users = [
317
+ {"name": "Alice Smith", "email": "alice@example.com"},
318
+ {"name": "Bob Johnson", "email": "bob@example.com"},
319
+ ]
320
+
321
+ results = engine.apply(users, SearchSpec(
322
+ query="alice",
323
+ fields=("name", "email"),
324
+ ))
325
+ # [Alice Smith]
326
+ ```
327
+
328
+ Fuzzy search (requires `pypaginate[search]`):
329
+
330
+ ```python
331
+ from pypaginate import SearchSpec, FuzzyMode
332
+
333
+ results = engine.apply(users, SearchSpec(
334
+ query="alce",
335
+ fields=("name",),
336
+ fuzzy=FuzzyMode.FUZZY,
337
+ threshold=75,
338
+ ))
339
+ ```
340
+
341
+ ## Pipeline (Filter + Sort + Search + Paginate)
342
+
343
+ Compose all operations in a single call:
344
+
345
+ ```python
346
+ from pypaginate import OffsetParams, FilterSpec, SortSpec, SortDirection
347
+ from pypaginate.engine.pipeline import SyncPipeline
348
+ from pypaginate.engine.paginator import Paginator
349
+ from pypaginate.adapters.memory import (
350
+ MemoryBackend,
351
+ MemoryFilterBackend,
352
+ MemorySortBackend,
353
+ )
354
+
355
+ pipeline = SyncPipeline(
356
+ Paginator(MemoryBackend()),
357
+ filter_backend=MemoryFilterBackend(),
358
+ sort_backend=MemorySortBackend(),
359
+ )
360
+
361
+ page = pipeline.execute(
362
+ users,
363
+ OffsetParams(page=1, limit=10),
364
+ filters=[FilterSpec(field="status", value="active")],
365
+ sorting=[SortSpec(field="name", direction=SortDirection.ASC)],
366
+ )
367
+ ```
368
+
369
+ For async (e.g., SQLAlchemy), use `AsyncPipeline` with `AsyncPaginator`.
370
+
371
+ ## Architecture
372
+
373
+ ```
374
+ pypaginate/
375
+ ├── domain/ # Models, specs, enums, protocols (no deps)
376
+ ├── engine/ # Paginator, cursor paginator, pipeline
377
+ ├── filtering/ # In-memory filter engine + operators
378
+ ├── sorting/ # In-memory sort engine
379
+ ├── search/ # In-memory search engine
380
+ └── adapters/
381
+ ├── memory/ # In-memory backends (filter, sort, search)
382
+ ├── sqlalchemy/ # SA backends (offset, cursor, filter, sort, search)
383
+ └── fastapi/ # Annotated dependencies (OffsetDep, FilterDep, etc.)
384
+ ```
385
+
386
+ ## Development
387
+
388
+ ```bash
389
+ git clone https://github.com/CybLow/pypaginate.git
390
+ cd pypaginate
391
+ uv sync
392
+
393
+ # Run all checks
394
+ uv run ruff format . && uv run ruff check --fix . && uv run mypy src/ && uv run pytest
395
+
396
+ # Individual commands
397
+ uv run pytest # Tests
398
+ uv run pytest --cov # Coverage
399
+ uv run ruff format . # Format
400
+ uv run ruff check --fix . # Lint
401
+ uv run mypy src/ # Type check
402
+ ```
403
+
404
+ ## Contributing
405
+
406
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
407
+
408
+ 1. Fork the repository
409
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
410
+ 3. Run tests and quality checks (`uv run pytest && uv run ruff check .`)
411
+ 4. Commit with conventional commits (`git commit -m 'feat: add amazing feature'`)
412
+ 5. Open a Pull Request
413
+
414
+ ## License
415
+
416
+ MIT -- see [LICENSE](LICENSE) for details.