cello-framework 1.2.0__tar.gz → 1.2.2__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 (288) hide show
  1. {cello_framework-1.2.0 → cello_framework-1.2.2}/.github/workflows/publish.yml +1 -0
  2. {cello_framework-1.2.0 → cello_framework-1.2.2}/CLAUDE.md +4 -2
  3. {cello_framework-1.2.0 → cello_framework-1.2.2}/Cargo.lock +1 -1
  4. {cello_framework-1.2.0 → cello_framework-1.2.2}/Cargo.toml +1 -1
  5. {cello_framework-1.2.0 → cello_framework-1.2.2}/PKG-INFO +1 -1
  6. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/overview.md +2 -2
  7. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/guides/error-handling.md +16 -33
  8. cello_framework-1.2.2/docs/releases/v1.2.1.md +167 -0
  9. cello_framework-1.2.2/docs/releases/v1.2.2.md +131 -0
  10. {cello_framework-1.2.0 → cello_framework-1.2.2}/mkdocs.yml +2 -0
  11. {cello_framework-1.2.0 → cello_framework-1.2.2}/pyproject.toml +1 -1
  12. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/__init__.py +6 -1
  13. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/guards.py +12 -6
  14. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/lib.rs +48 -50
  15. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/auth.rs +4 -4
  16. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/csrf.rs +4 -3
  17. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/guards.rs +2 -2
  18. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/mod.rs +13 -0
  19. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/rate_limit.rs +5 -5
  20. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/session.rs +3 -3
  21. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/minijinja_engine.rs +5 -5
  22. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/test_cello.py +3 -3
  23. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/test_minijinja.py +1 -1
  24. {cello_framework-1.2.0 → cello_framework-1.2.2}/.github/workflows/ci.yml +0 -0
  25. {cello_framework-1.2.0 → cello_framework-1.2.2}/.github/workflows/docs.yml +0 -0
  26. {cello_framework-1.2.0 → cello_framework-1.2.2}/.gitignore +0 -0
  27. {cello_framework-1.2.0 → cello_framework-1.2.2}/CONTRIBUTING.md +0 -0
  28. {cello_framework-1.2.0 → cello_framework-1.2.2}/ENTERPRISE_ROADMAP.md +0 -0
  29. {cello_framework-1.2.0 → cello_framework-1.2.2}/LICENSE +0 -0
  30. {cello_framework-1.2.0 → cello_framework-1.2.2}/PUBLISHING.md +0 -0
  31. {cello_framework-1.2.0 → cello_framework-1.2.2}/README.md +0 -0
  32. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/README.md +0 -0
  33. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/benchmark.py +0 -0
  34. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/README.md +0 -0
  35. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/apps/__init__.py +0 -0
  36. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/apps/blacksheep_app.py +0 -0
  37. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/apps/cello_app.py +0 -0
  38. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/apps/fastapi_app.py +0 -0
  39. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/apps/robyn_app.py +0 -0
  40. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/requirements.txt +0 -0
  41. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/compare/run_benchmarks.py +0 -0
  42. {cello_framework-1.2.0 → cello_framework-1.2.2}/benchmarks/quick_bench.py +0 -0
  43. {cello_framework-1.2.0 → cello_framework-1.2.2}/cello.md +0 -0
  44. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/community/code-of-conduct.md +0 -0
  45. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/community/contributing.md +0 -0
  46. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/community/index.md +0 -0
  47. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/community/support.md +0 -0
  48. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/deployment/docker.md +0 -0
  49. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/deployment/kubernetes.md +0 -0
  50. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/deployment/service-mesh.md +0 -0
  51. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/index.md +0 -0
  52. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/integration/database.md +0 -0
  53. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/integration/graphql.md +0 -0
  54. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/integration/grpc.md +0 -0
  55. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/integration/message-queues.md +0 -0
  56. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/observability/health-checks.md +0 -0
  57. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/observability/metrics.md +0 -0
  58. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/observability/opentelemetry.md +0 -0
  59. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/observability/tracing.md +0 -0
  60. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/enterprise/roadmap.md +0 -0
  61. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/background-tasks.md +0 -0
  62. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/file-storage.md +0 -0
  63. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/fullstack.md +0 -0
  64. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/graphql.md +0 -0
  65. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/microservices.md +0 -0
  66. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/realtime-dashboard.md +0 -0
  67. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/advanced/redis-caching.md +0 -0
  68. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/basic/database.md +0 -0
  69. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/basic/forms.md +0 -0
  70. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/basic/hello-world.md +0 -0
  71. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/basic/jwt-auth.md +0 -0
  72. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/basic/query-params.md +0 -0
  73. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/basic/rest-api.md +0 -0
  74. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/enterprise/api-gateway.md +0 -0
  75. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/enterprise/event-sourcing.md +0 -0
  76. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/enterprise/health-checks.md +0 -0
  77. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/enterprise/multi-tenant.md +0 -0
  78. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/enterprise/oauth2.md +0 -0
  79. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/enterprise/rate-limiting.md +0 -0
  80. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/index.md +0 -0
  81. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/adaptive-rate-limiting.md +0 -0
  82. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/advanced-middleware.md +0 -0
  83. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/advanced-patterns.md +0 -0
  84. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/all-features.md +0 -0
  85. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/api-protocols.md +0 -0
  86. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/async-handlers.md +0 -0
  87. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/auto-openapi.md +0 -0
  88. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/blueprints-advanced.md +0 -0
  89. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/circuit-breaker.md +0 -0
  90. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/cluster-demo.md +0 -0
  91. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/complete-showcase.md +0 -0
  92. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/comprehensive-demo.md +0 -0
  93. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/database-demo.md +0 -0
  94. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/dto-validation.md +0 -0
  95. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/enterprise-config.md +0 -0
  96. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/guards.md +0 -0
  97. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/hello-world.md +0 -0
  98. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/lifecycle-hooks.md +0 -0
  99. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/middleware-demo.md +0 -0
  100. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/minijinja-advanced.md +0 -0
  101. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/minijinja-basic.md +0 -0
  102. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/minijinja-blog.md +0 -0
  103. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/minijinja-emails.md +0 -0
  104. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/minijinja-forms.md +0 -0
  105. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/minijinja-macros.md +0 -0
  106. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/security.md +0 -0
  107. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/simple-api.md +0 -0
  108. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/smart-caching.md +0 -0
  109. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/examples/real/streaming-sse.md +0 -0
  110. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/advanced/background-tasks.md +0 -0
  111. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/advanced/dependency-injection.md +0 -0
  112. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/advanced/dto-validation.md +0 -0
  113. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/advanced/file-uploads.md +0 -0
  114. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/advanced/static-files.md +0 -0
  115. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/advanced/templates.md +0 -0
  116. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/core/async.md +0 -0
  117. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/core/blueprints.md +0 -0
  118. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/core/requests.md +0 -0
  119. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/core/responses.md +0 -0
  120. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/core/routing.md +0 -0
  121. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/index.md +0 -0
  122. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/caching.md +0 -0
  123. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/circuit-breaker.md +0 -0
  124. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/compression.md +0 -0
  125. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/cors.md +0 -0
  126. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/logging.md +0 -0
  127. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/overview.md +0 -0
  128. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/middleware/rate-limiting.md +0 -0
  129. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/minijinja-templates.md +0 -0
  130. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/realtime/sse.md +0 -0
  131. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/realtime/websocket.md +0 -0
  132. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/authentication.md +0 -0
  133. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/csrf.md +0 -0
  134. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/guards.md +0 -0
  135. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/headers.md +0 -0
  136. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/jwt.md +0 -0
  137. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/security/sessions.md +0 -0
  138. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/features/templates.md +0 -0
  139. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/getting-started/configuration.md +0 -0
  140. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/getting-started/first-app.md +0 -0
  141. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/getting-started/index.md +0 -0
  142. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/getting-started/installation.md +0 -0
  143. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/getting-started/project-structure.md +0 -0
  144. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/getting-started/quickstart.md +0 -0
  145. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/includes/abbreviations.md +0 -0
  146. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/index.md +0 -0
  147. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/javascripts/extra.js +0 -0
  148. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/guides/best-practices.md +0 -0
  149. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/guides/deployment.md +0 -0
  150. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/guides/performance.md +0 -0
  151. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/guides/testing.md +0 -0
  152. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/index.md +0 -0
  153. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/patterns/cqrs.md +0 -0
  154. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/patterns/event-driven.md +0 -0
  155. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/patterns/repository.md +0 -0
  156. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/patterns/service-layer.md +0 -0
  157. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/tutorials/auth-system.md +0 -0
  158. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/tutorials/chat-app.md +0 -0
  159. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/tutorials/microservices.md +0 -0
  160. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/learn/tutorials/rest-api.md +0 -0
  161. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/logo-full.png +0 -0
  162. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/logo-icon.svg +0 -0
  163. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/logo.jpg +0 -0
  164. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/logo.svg +0 -0
  165. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/overrides/main.html +0 -0
  166. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/app.md +0 -0
  167. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/blueprint.md +0 -0
  168. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/context.md +0 -0
  169. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/guards.md +0 -0
  170. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/middleware.md +0 -0
  171. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/request.md +0 -0
  172. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/api/response.md +0 -0
  173. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/cli.md +0 -0
  174. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/config/middleware.md +0 -0
  175. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/config/security.md +0 -0
  176. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/config/server.md +0 -0
  177. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/errors.md +0 -0
  178. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/reference/index.md +0 -0
  179. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/changelog.md +0 -0
  180. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/index.md +0 -0
  181. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/migration.md +0 -0
  182. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.10.0.md +0 -0
  183. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.3.0.md +0 -0
  184. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.4.0.md +0 -0
  185. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.5.0.md +0 -0
  186. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.6.0.md +0 -0
  187. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.7.0.md +0 -0
  188. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.8.0.md +0 -0
  189. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v0.9.0.md +0 -0
  190. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v1.0.0.md +0 -0
  191. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v1.0.1.md +0 -0
  192. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v1.1.0.md +0 -0
  193. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/releases/v1.2.0.md +0 -0
  194. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/requirements.txt +0 -0
  195. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/stylesheets/extra.css +0 -0
  196. {cello_framework-1.2.0 → cello_framework-1.2.2}/docs/tags.md +0 -0
  197. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/adaptive_rate_limit.py +0 -0
  198. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/advanced.py +0 -0
  199. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/advanced_middleware.py +0 -0
  200. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/advanced_patterns_demo.py +0 -0
  201. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/all_features_demo.py +0 -0
  202. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/api_protocols_demo.py +0 -0
  203. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/async_demo.py +0 -0
  204. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/auto_openapi_demo.py +0 -0
  205. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/circuit_breaker.py +0 -0
  206. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/cluster_demo.py +0 -0
  207. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/complete_showcase.py +0 -0
  208. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/comprehensive_demo.py +0 -0
  209. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/database_demo.py +0 -0
  210. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/dto_validation.py +0 -0
  211. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/enterprise.py +0 -0
  212. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/guards.py +0 -0
  213. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/hello.py +0 -0
  214. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/lifecycle_hooks.py +0 -0
  215. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/middleware_demo.py +0 -0
  216. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/minijinja_advanced.py +0 -0
  217. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/minijinja_basic.py +0 -0
  218. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/minijinja_blog.py +0 -0
  219. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/minijinja_emails.py +0 -0
  220. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/minijinja_forms.py +0 -0
  221. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/minijinja_macros.py +0 -0
  222. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/security.py +0 -0
  223. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/simple_api.py +0 -0
  224. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/smart_caching.py +0 -0
  225. {cello_framework-1.2.0 → cello_framework-1.2.2}/examples/streaming_demo.py +0 -0
  226. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/cqrs.py +0 -0
  227. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/database.py +0 -0
  228. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/eventsourcing.py +0 -0
  229. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/graphql.py +0 -0
  230. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/grpc.py +0 -0
  231. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/messaging.py +0 -0
  232. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/saga.py +0 -0
  233. {cello_framework-1.2.0 → cello_framework-1.2.2}/python/cello/validation.py +0 -0
  234. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/arena.rs +0 -0
  235. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/background.rs +0 -0
  236. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/blueprint.rs +0 -0
  237. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/context.rs +0 -0
  238. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/dependency.rs +0 -0
  239. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/dto.rs +0 -0
  240. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/error.rs +0 -0
  241. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/handler.rs +0 -0
  242. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/http_client.rs +0 -0
  243. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/json.rs +0 -0
  244. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/lifecycle.rs +0 -0
  245. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/body_limit.rs +0 -0
  246. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/cache.rs +0 -0
  247. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/circuit_breaker.rs +0 -0
  248. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/cors.rs +0 -0
  249. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/cqrs.rs +0 -0
  250. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/database.rs +0 -0
  251. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/etag.rs +0 -0
  252. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/eventsourcing.rs +0 -0
  253. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/exception_handler.rs +0 -0
  254. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/graphql.rs +0 -0
  255. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/grpc.rs +0 -0
  256. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/health.rs +0 -0
  257. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/messaging.rs +0 -0
  258. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/prometheus.rs +0 -0
  259. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/redis.rs +0 -0
  260. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/request_id.rs +0 -0
  261. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/saga.rs +0 -0
  262. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/security.rs +0 -0
  263. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/static_files.rs +0 -0
  264. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/middleware/telemetry.rs +0 -0
  265. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/multipart.rs +0 -0
  266. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/openapi.rs +0 -0
  267. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/request/mod.rs +0 -0
  268. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/request/multipart_streaming.rs +0 -0
  269. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/request/parsing.rs +0 -0
  270. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/response/mod.rs +0 -0
  271. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/response/streaming.rs +0 -0
  272. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/response/xml.rs +0 -0
  273. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/router.rs +0 -0
  274. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/routing/constraints.rs +0 -0
  275. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/routing/mod.rs +0 -0
  276. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/server/cluster.rs +0 -0
  277. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/server/mod.rs +0 -0
  278. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/server/protocols.rs +0 -0
  279. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/sse.rs +0 -0
  280. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/template.rs +0 -0
  281. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/timeout.rs +0 -0
  282. {cello_framework-1.2.0 → cello_framework-1.2.2}/src/websocket.rs +0 -0
  283. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/verify_adaptive.py +0 -0
  284. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/verify_caching.py +0 -0
  285. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/verify_circuit_breaker.py +0 -0
  286. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/verify_dto.py +0 -0
  287. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/verify_guards_impl.py +0 -0
  288. {cello_framework-1.2.0 → cello_framework-1.2.2}/tests/verify_lifecycle.py +0 -0
@@ -165,6 +165,7 @@ jobs:
165
165
  uses: pypa/gh-action-pypi-publish@release/v1
166
166
  with:
167
167
  packages-dir: dist/
168
+ skip-existing: true
168
169
 
169
170
  # ============================================================================
170
171
  # Publish to Test PyPI (manual trigger)
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Cello** is an ultra-fast, Rust-powered Python async web framework designed to achieve C-level performance on the hot path while maintaining Python's developer experience. It's the successor to frameworks like FastAPI, Robyn, and Litestar, combining their best features with pure Rust implementation for maximum performance.
6
6
 
7
- **Version:** 1.1.0
7
+ **Version:** 1.2.2
8
8
  **License:** MIT
9
9
  **Python Requirement:** 3.12+
10
10
  **Author:** Jagadeesh Katla
@@ -336,7 +336,7 @@ from cello import App, ProblemDetails
336
336
  @app.exception_handler(ValueError)
337
337
  def handle_value_error(request, exc):
338
338
  return ProblemDetails(
339
- type_url="/errors/validation",
339
+ type_uri="/errors/validation",
340
340
  title="Validation Error",
341
341
  status=400,
342
342
  detail=str(exc),
@@ -346,6 +346,8 @@ def handle_value_error(request, exc):
346
346
 
347
347
  ## Version History
348
348
 
349
+ - **v1.2.2**: Security & bug fixes — CSRF `HttpOnly` on double-submit cookie (critical, broke all AJAX CSRF); all middleware `skip_path` prefix bypass fixed via `path_matches_skip()` helper; `FixedWindowStore` window_start never updated after reset; unused `mut` cleaned in minijinja tests
350
+ - **v1.2.1**: Bug fixes — server port never bound (`pyo3_asyncio` replaced with native `py.allow_threads` + `tokio::block_on`); `ProblemDetails` was missing from Python module export; `And`/`Or` guards now accept both `*args` and list styles; CSRF `HttpOnly` removed from double-submit cookie (JS must read it); `FixedWindowStore` window_start never updated after reset; all middleware `skip_path` used raw `starts_with` allowing prefix bypass; doc corrections (`type_uri` not `type_url`)
349
351
  - **v1.2.0**: Bug fixes (shutdown coroutine never awaited, KeyboardInterrupt in shutdown handler, `request.redis` AttributeError); Redis Lua scripting (`eval`, `evalsha`, `script_load`); Rust-native `AsyncClient` backed by `reqwest + Tokio` — GIL never held during HTTP I/O, HTTP/2, gzip, rustls
350
352
  - **v1.1.0**: MiniJinja Jinja2-compatible template engine (`MiniJinjaEngine`, `App.enable_templates()`, `App.render()`, `App.render_string()`); minijinja 2 Rust crate; HTML auto-escaping; globals; 47 new tests; 6 examples
351
353
  - **v1.0.1**: Cross-platform fixes (Windows multi-worker, signal handling, UNC paths; ARM JSON fallback; Linux-only CPU affinity), async compatibility fixes (handler validation, guards, cache decorator, blueprints), guards and database exports in `__all__`
@@ -341,7 +341,7 @@ dependencies = [
341
341
 
342
342
  [[package]]
343
343
  name = "cello-framework"
344
- version = "1.2.0"
344
+ version = "1.2.2"
345
345
  dependencies = [
346
346
  "async-graphql",
347
347
  "async-graphql-value",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cello-framework"
3
- version = "1.2.0"
3
+ version = "1.2.2"
4
4
  edition = "2021"
5
5
  description = "Ultra-fast Rust-powered Python async web framework"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cello-framework
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -270,8 +270,8 @@ admin_only = RoleGuard(["admin"])
270
270
  can_edit = PermissionGuard(["posts:edit"])
271
271
 
272
272
  # Composable guards
273
- admin_or_editor = Or(RoleGuard(["admin"]), RoleGuard(["editor"]))
274
- admin_with_write = And(RoleGuard(["admin"]), PermissionGuard(["write"]))
273
+ admin_or_editor = Or([RoleGuard(["admin"]), RoleGuard(["editor"])])
274
+ admin_with_write = And([RoleGuard(["admin"]), PermissionGuard(["write"])])
275
275
 
276
276
  @app.get("/admin", guards=[admin_only])
277
277
  def admin_panel(request):
@@ -116,49 +116,32 @@ def handle_unexpected(request, exc):
116
116
  ### Using ProblemDetails in Cello
117
117
 
118
118
  ```python
119
- from cello import App, Response
119
+ from cello import App, ProblemDetails
120
120
 
121
121
  app = App()
122
122
 
123
- def problem(type_url: str, title: str, status: int,
124
- detail: str = None, instance: str = None, **extra) -> Response:
125
- """Helper to build an RFC 7807 response."""
126
- body = {"type": type_url, "title": title, "status": status}
127
- if detail:
128
- body["detail"] = detail
129
- if instance:
130
- body["instance"] = instance
131
- body.update(extra)
132
- resp = Response.json(body, status=status)
133
- resp.set_header("Content-Type", "application/problem+json")
134
- return resp
135
- ```
136
-
137
- Use the helper in handlers and exception handlers:
138
-
139
- ```python
140
123
  @app.get("/users/{id}")
141
124
  def get_user(request):
142
125
  user = find_user(request.params["id"])
143
126
  if not user:
144
- return problem(
145
- type_url="/errors/not-found",
127
+ return ProblemDetails(
128
+ type_uri="/errors/not-found",
146
129
  title="User Not Found",
147
130
  status=404,
148
131
  detail=f"No user with ID {request.params['id']}",
149
132
  instance=request.path,
150
- )
133
+ ).to_response()
151
134
  return user
152
135
 
153
136
  @app.exception_handler(ValueError)
154
137
  def handle_validation(request, exc):
155
- return problem(
156
- type_url="/errors/validation",
138
+ return ProblemDetails(
139
+ type_uri="/errors/validation",
157
140
  title="Validation Error",
158
141
  status=400,
159
142
  detail=str(exc),
160
143
  instance=request.path,
161
- )
144
+ ).to_response()
162
145
  ```
163
146
 
164
147
  ---
@@ -213,22 +196,22 @@ class ConflictError(Exception):
213
196
 
214
197
  @app.exception_handler(NotFoundError)
215
198
  def handle_not_found(request, exc):
216
- return problem(
217
- type_url="/errors/not-found",
199
+ return ProblemDetails(
200
+ type_uri="/errors/not-found",
218
201
  title=f"{exc.resource} Not Found",
219
202
  status=404,
220
203
  detail=str(exc),
221
204
  instance=request.path,
222
- )
205
+ ).to_response()
223
206
 
224
207
  @app.exception_handler(ConflictError)
225
208
  def handle_conflict(request, exc):
226
- return problem(
227
- type_url="/errors/conflict",
209
+ return ProblemDetails(
210
+ type_uri="/errors/conflict",
228
211
  title="Conflict",
229
212
  status=409,
230
213
  detail=str(exc),
231
- )
214
+ ).to_response()
232
215
  ```
233
216
 
234
217
  Then raise them in handlers:
@@ -253,12 +236,12 @@ from cello.guards import GuardError
253
236
 
254
237
  @app.exception_handler(GuardError)
255
238
  def handle_guard_error(request, exc):
256
- return problem(
257
- type_url="/errors/access-denied",
239
+ return ProblemDetails(
240
+ type_uri="/errors/access-denied",
258
241
  title="Access Denied",
259
242
  status=exc.status_code,
260
243
  detail=exc.message,
261
- )
244
+ ).to_response()
262
245
  ```
263
246
 
264
247
  ---
@@ -0,0 +1,167 @@
1
+ ---
2
+ title: v1.2.1 Release Notes
3
+ description: Cello Framework v1.2.1 — Critical Bug Fixes
4
+ tags:
5
+ - v1.2.1
6
+ - Release Notes
7
+ - Bug Fixes
8
+ - Changelog
9
+ ---
10
+
11
+ # Cello v1.2.1 — Critical Bug Fixes
12
+
13
+ **Release Date:** June 14, 2026
14
+ **License:** MIT
15
+ **Python:** 3.12+
16
+
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ Cello v1.2.1 is a patch release fixing critical bugs discovered after v1.2.0: the HTTP server
22
+ never bound its port, `ProblemDetails` was inaccessible from Python, and `And`/`Or` guards
23
+ rejected the `And(g1, g2)` call style. All changes are fully backwards-compatible.
24
+
25
+ ---
26
+
27
+ ## Bug Fixes
28
+
29
+ ### Server Port Never Bound
30
+
31
+ **Symptom:** `app.run()` started without error but the server never listened on any port —
32
+ `ss -tlnp` showed nothing, all connections were refused.
33
+
34
+ **Root cause:** `pyo3_asyncio::tokio::run` drives Tokio socket I/O through Python's asyncio
35
+ selector loop. In Python 3.12+ with pyo3 0.20, the two event loops do not integrate correctly:
36
+ the asyncio loop never ticks the Tokio I/O reactor, so `TcpListener::accept()` never resolves
37
+ and the port never appears in the OS socket table.
38
+
39
+ **Fix:** Replaced `pyo3_asyncio::tokio::run(py, ...)` with `py.allow_threads(|| runtime.block_on(...))`.
40
+ The GIL is released for the duration of the server run; Tokio drives all I/O natively. Python
41
+ handlers re-acquire the GIL individually via `Python::with_gil` when invoked.
42
+
43
+ ```python
44
+ # Before (broken — port never bound)
45
+ app.run(port=8000) # silently hangs
46
+
47
+ # After (fixed)
48
+ app.run(port=8000) # binds and serves immediately
49
+ ```
50
+
51
+ ---
52
+
53
+ ### `ProblemDetails` Not Importable
54
+
55
+ **Symptom:** `from cello import ProblemDetails` raised `ImportError`.
56
+
57
+ **Root cause:** `ProblemDetails` had `#[pyclass]` in `src/error.rs` but was never registered
58
+ with `m.add_class::<error::ProblemDetails>()` in the PyO3 module, and was missing from
59
+ `python/cello/__init__.py`.
60
+
61
+ **Fix:** Added to module registration and Python exports.
62
+
63
+ ```python
64
+ # Now works
65
+ from cello import ProblemDetails
66
+
67
+ @app.exception_handler(ValueError)
68
+ def handle_error(request, exc):
69
+ return ProblemDetails(
70
+ type_uri="/errors/validation",
71
+ title="Validation Error",
72
+ status=400,
73
+ detail=str(exc),
74
+ instance=request.path,
75
+ ).to_response()
76
+ ```
77
+
78
+ !!! note "Field name is `type_uri`, not `type_url`"
79
+ The constructor parameter follows the RFC 7807 naming convention.
80
+ Use `type_uri=`, not `type_url=`.
81
+
82
+ ---
83
+
84
+ ### `And`/`Or` Guards Rejected `*args` Call Style
85
+
86
+ **Symptom:** `And(guard1, guard2)` raised `TypeError: And.__init__() takes 2 positional arguments but 3 were given`.
87
+
88
+ **Root cause:** `And` and `Or` only accepted a single list argument.
89
+
90
+ **Fix:** Both now accept either a list or `*args`.
91
+
92
+ ```python
93
+ # Both styles now work
94
+ admin_and_write = And([RoleGuard(["admin"]), PermissionGuard(["write"])])
95
+ admin_and_write = And(RoleGuard(["admin"]), PermissionGuard(["write"]))
96
+
97
+ admin_or_editor = Or([RoleGuard(["admin"]), RoleGuard(["editor"])])
98
+ admin_or_editor = Or(RoleGuard(["admin"]), RoleGuard(["editor"]))
99
+ ```
100
+
101
+ ---
102
+
103
+ ### CSRF Cookie Readable by JavaScript (Security)
104
+
105
+ **Symptom:** AJAX/SPA applications using CSRF protection failed all POST/PUT/DELETE requests with 403.
106
+
107
+ **Root cause:** The CSRF double-submit cookie was set with the `HttpOnly` flag. This prevents
108
+ JavaScript from reading the cookie value, making it impossible for the frontend to copy it into
109
+ the `X-CSRF-Token` header — which is the entire point of the double-submit pattern.
110
+
111
+ **Fix:** Removed `HttpOnly` from the CSRF cookie. The CSRF cookie is intentionally readable by
112
+ JavaScript; the session cookie (where sensitive data lives) remains `HttpOnly`.
113
+
114
+ ```javascript
115
+ // Now works — JS can read the CSRF cookie and send it back
116
+ const csrf = document.cookie.match(/_csrf=([^;]+)/)?.[1];
117
+ fetch("/api/data", {
118
+ method: "POST",
119
+ headers: { "X-CSRF-Token": csrf },
120
+ body: JSON.stringify(data),
121
+ });
122
+ ```
123
+
124
+ ---
125
+
126
+ ### Rate Limiter Fixed-Window Counter Reset (Bug)
127
+
128
+ **Symptom:** After the first rate-limit window expired, the fixed-window counter reset to zero
129
+ on every single subsequent request, making rate limiting completely ineffective.
130
+
131
+ **Root cause:** When the time window rolled over, `entry.count` was reset to 0 but
132
+ `entry.window_start` was never updated to the new window. Every request in the new window
133
+ saw `window_start != current_window` and reset the counter again.
134
+
135
+ **Fix:** Update `entry.window_start = current_window` alongside the count reset.
136
+
137
+ ---
138
+
139
+ ### Auth Skip-Path Prefix Bypass (Security)
140
+
141
+ **Symptom:** Calling `skip_path("/health")` to exclude the health endpoint from authentication
142
+ also excluded `/healthz`, `/healthy`, and any other path starting with `/health`.
143
+
144
+ **Root cause:** Path matching used `request.path.starts_with(skip_path)` without checking that
145
+ the following character is `/` or end-of-string.
146
+
147
+ **Fix:** Path is now only skipped when it equals the pattern exactly or starts with
148
+ `{pattern}/`. Affects `JwtAuth`, `BasicAuth`, `ApiKeyAuth`, `RateLimitMiddleware`,
149
+ `SessionMiddleware`, `CsrfMiddleware`, and `GuardsMiddleware`.
150
+
151
+ ```python
152
+ # Before: /healthz would also be skipped (security bypass)
153
+ jwt.skip_path("/health")
154
+
155
+ # After: only /health and /health/* are skipped
156
+ jwt.skip_path("/health")
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Upgrade
162
+
163
+ ```bash
164
+ pip install --upgrade cello-framework==1.2.1
165
+ ```
166
+
167
+ No API changes — drop-in replacement for v1.2.0.
@@ -0,0 +1,131 @@
1
+ ---
2
+ title: v1.2.2 Release Notes
3
+ description: Cello Framework v1.2.2 — Security & Bug Fixes
4
+ tags:
5
+ - v1.2.2
6
+ - Release Notes
7
+ - Security
8
+ - Bug Fixes
9
+ ---
10
+
11
+ # Cello v1.2.2 — Security & Bug Fixes
12
+
13
+ **Release Date:** June 14, 2026
14
+ **License:** MIT
15
+ **Python:** 3.12+
16
+
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ Cello v1.2.2 is a patch release that fixes one critical security bug, one security
22
+ vulnerability, and two correctness bugs discovered during a thorough code review of v1.2.1.
23
+ All changes are fully backwards-compatible.
24
+
25
+ **Upgrade immediately** if you use CSRF protection or authentication middleware skip-paths.
26
+
27
+ ---
28
+
29
+ ## Security Fixes
30
+
31
+ ### CSRF Double-Submit Cookie Was `HttpOnly` (Critical)
32
+
33
+ **Symptom:** AJAX applications and SPAs using `CsrfMiddleware` received 403 on every
34
+ POST/PUT/DELETE request, even when the CSRF token was correctly set.
35
+
36
+ **Root cause:** The CSRF double-submit cookie was set with `HttpOnly`, which prevents
37
+ JavaScript from reading cookie values. The double-submit pattern *requires* JavaScript
38
+ to read the cookie and copy its value into the `X-CSRF-Token` request header. With
39
+ `HttpOnly` set, this was impossible — every state-changing request failed CSRF validation.
40
+
41
+ **Fix:** Removed `HttpOnly` from the CSRF token cookie. The CSRF cookie is intentionally
42
+ readable by JavaScript (that is the design). Your session cookie, which holds sensitive
43
+ data, remains `HttpOnly`.
44
+
45
+ ```javascript
46
+ // Now works — JavaScript can read the CSRF cookie
47
+ const csrf = document.cookie.match(/_csrf=([^;]+)/)?.[1];
48
+
49
+ fetch("/api/submit", {
50
+ method: "POST",
51
+ headers: { "X-CSRF-Token": csrf, "Content-Type": "application/json" },
52
+ body: JSON.stringify({ name: "Alice" }),
53
+ });
54
+ ```
55
+
56
+ ---
57
+
58
+ ### Auth Middleware `skip_path` Allowed Prefix Bypass (Security)
59
+
60
+ **Symptom:** Calling `jwt.skip_path("/health")` to exclude the health-check endpoint
61
+ from authentication also excluded `/healthz`, `/healthy`, `/health-admin`, and any
62
+ other path whose string representation started with `/health`.
63
+
64
+ **Root cause:** All middleware used `request.path.starts_with(skip_path)` with no
65
+ boundary check. An attacker aware of a skipped path could craft a URL that bypasses
66
+ authentication.
67
+
68
+ **Affected middleware:** `JwtAuth`, `BasicAuth`, `ApiKeyAuth`, `RateLimitMiddleware`,
69
+ `SessionMiddleware`, `CsrfMiddleware`, `GuardsMiddleware`.
70
+
71
+ **Fix:** Paths are now only skipped when:
72
+
73
+ - The path **exactly equals** the pattern, or
74
+ - The path **starts with** `{pattern}/` (sub-path match).
75
+
76
+ ```python
77
+ # Before: /healthz was also skipped — security bypass
78
+ jwt_auth.skip_path("/health")
79
+
80
+ # After: only /health and /health/* are skipped
81
+ jwt_auth.skip_path("/health")
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Bug Fixes
87
+
88
+ ### Rate Limiter Fixed-Window Counter Broke After First Window
89
+
90
+ **Symptom:** After the first time-window expired, the `FixedWindowStore` rate limiter
91
+ stopped enforcing limits entirely — every request was allowed regardless of how many
92
+ had been made.
93
+
94
+ **Root cause:** When the window rolled over, the per-client entry's `count` was reset
95
+ to 0 but `window_start` was never updated to the new window ID. Every subsequent
96
+ request also saw `window_start != current_window` and reset the counter to 0 again,
97
+ making the limiter permanently ineffective after the first window.
98
+
99
+ **Fix:** `window_start` is now updated to `current_window` alongside the count reset.
100
+
101
+ ---
102
+
103
+ ### Unused `mut` Warnings in Template Engine Tests
104
+
105
+ Five test functions in `minijinja_engine.rs` declared `let mut env = Environment::new()`
106
+ where `mut` was not needed (the variable was never mutated after construction). These
107
+ were cleaned up to eliminate Clippy warnings.
108
+
109
+ ---
110
+
111
+ ## Files Changed
112
+
113
+ | File | Change |
114
+ |------|--------|
115
+ | `src/middleware/csrf.rs` | Remove `HttpOnly` from CSRF cookie |
116
+ | `src/middleware/mod.rs` | Add `path_matches_skip()` shared helper |
117
+ | `src/middleware/auth.rs` | Use `path_matches_skip()` in JWT/Basic/ApiKey |
118
+ | `src/middleware/session.rs` | Use `path_matches_skip()` |
119
+ | `src/middleware/rate_limit.rs` | Use `path_matches_skip()`; fix `window_start` reset |
120
+ | `src/middleware/guards.rs` | Use `path_matches_skip()` |
121
+ | `src/minijinja_engine.rs` | Remove unused `mut` from test bindings |
122
+
123
+ ---
124
+
125
+ ## Upgrade
126
+
127
+ ```bash
128
+ pip install --upgrade cello-framework==1.2.2
129
+ ```
130
+
131
+ Drop-in replacement for v1.2.1. No API changes.
@@ -364,6 +364,8 @@ nav:
364
364
 
365
365
  - Release Notes:
366
366
  - releases/index.md
367
+ - v1.2.2: releases/v1.2.2.md
368
+ - v1.2.1: releases/v1.2.1.md
367
369
  - v1.2.0: releases/v1.2.0.md
368
370
  - v1.1.0: releases/v1.1.0.md
369
371
  - v1.0.1: releases/v1.0.1.md
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "cello-framework"
7
- version = "1.2.0"
7
+ version = "1.2.2"
8
8
  description = "Ultra-fast Rust-powered Python async web framework"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -143,6 +143,9 @@ from cello._cello import (
143
143
  HttpResponse,
144
144
  )
145
145
 
146
+ # RFC 7807 Problem Details
147
+ from cello._cello import ProblemDetails
148
+
146
149
  def validate_jwt_config(config: JwtConfig) -> JwtConfig:
147
150
  """Validate a JwtConfig instance.
148
151
 
@@ -285,13 +288,15 @@ __all__ = [
285
288
  "EventSourcingConfig",
286
289
  "CqrsConfig",
287
290
  "SagaConfig",
291
+ # RFC 7807
292
+ "ProblemDetails",
288
293
  # Config validators
289
294
  "validate_jwt_config",
290
295
  "validate_session_config",
291
296
  "validate_rate_limit_config",
292
297
  "validate_tls_config",
293
298
  ]
294
- __version__ = "1.2.0"
299
+ __version__ = "1.2.2"
295
300
 
296
301
 
297
302
  class Blueprint:
@@ -124,9 +124,12 @@ class Authenticated(Guard):
124
124
  return True
125
125
 
126
126
  class And(Guard):
127
- """Pass only if ALL guards pass."""
128
- def __init__(self, guards: List[Callable]):
129
- self.guards = guards
127
+ """Pass only if ALL guards pass. Accepts a list or *args: And([g1, g2]) or And(g1, g2)."""
128
+ def __init__(self, *args):
129
+ if len(args) == 1 and isinstance(args[0], list):
130
+ self.guards = args[0]
131
+ else:
132
+ self.guards = list(args)
130
133
 
131
134
  def __call__(self, request: Any):
132
135
  for guard in self.guards:
@@ -139,9 +142,12 @@ class And(Guard):
139
142
  return True
140
143
 
141
144
  class Or(Guard):
142
- """Pass if ANY guard passes."""
143
- def __init__(self, guards: List[Callable]):
144
- self.guards = guards
145
+ """Pass if ANY guard passes. Accepts a list or *args: Or([g1, g2]) or Or(g1, g2)."""
146
+ def __init__(self, *args):
147
+ if len(args) == 1 and isinstance(args[0], list):
148
+ self.guards = args[0]
149
+ else:
150
+ self.guards = list(args)
145
151
 
146
152
  def __call__(self, request: Any):
147
153
  last_error = None
@@ -754,60 +754,55 @@ def openapi_handler(request):
754
754
  let startup_handlers = self.startup_handlers.clone();
755
755
  let shutdown_handlers = self.shutdown_handlers.clone();
756
756
 
757
- // Configure pyo3-asyncio to use a single-threaded Tokio runtime.
757
+ // Release the GIL and run a native Tokio current-thread runtime.
758
758
  //
759
- // PERF: current_thread avoids GIL contention N Tokio threads all competing
760
- // for Python::with_gil creates massive serialization overhead. Parallelism comes
761
- // from multi-process (fork + SO_REUSEPORT on Unix, subprocess on Windows).
762
- //
763
- // After this call, asyncio.get_event_loop() inside any Python handler returns the
764
- // pyo3-asyncio Tokio-backed loop instead of _UnixSelectorEventLoop, proving that
765
- // Python coroutines are now driven by Tokio. The GIL is released during I/O waits
766
- // within coroutines — no uvloop needed, no external event loop optimisation.
767
- let mut builder = tokio::runtime::Builder::new_current_thread();
768
- builder.enable_all();
769
- pyo3_asyncio::tokio::init(builder);
770
-
771
- // pyo3_asyncio::tokio::run creates a new Python asyncio event loop backed by
772
- // our Tokio runtime, then runs the given future as a coroutine on that loop.
773
- // The GIL is managed internally: released when Tokio is waiting, re-acquired
774
- // only when Python bytecode needs to execute.
775
- pyo3_asyncio::tokio::run(py, async move {
776
- let mut config = server::ServerConfig::new(&host_owned, port);
777
- config.workers = workers.unwrap_or(0);
778
-
779
- let server = Server::new(
780
- config,
781
- router,
782
- handlers,
783
- middleware,
784
- websocket_handlers,
785
- dependency_container,
786
- guards,
787
- prometheus,
788
- );
789
-
790
- // Startup hooks — driven by Tokio so async def handlers work without asyncio.run()
791
- for handler in &startup_handlers {
792
- if let Err(e) = run_lifecycle_handler_async(handler.clone()).await {
793
- eprintln!("Error in startup handler: {e}");
794
- }
795
- }
759
+ // pyo3_asyncio::tokio::run was previously used here but it drives Tokio I/O
760
+ // through Python's asyncio selector loop, which breaks socket binding in
761
+ // environments where the two event loops don't integrate (Python 3.12+ / pyo3 0.20).
762
+ // Since the server's hot path is pure Rust I/O, we release the GIL with
763
+ // allow_threads and block on a self-contained Tokio runtime. Python handlers
764
+ // re-acquire the GIL individually via Python::with_gil when they need it.
765
+ py.allow_threads(|| {
766
+ tokio::runtime::Builder::new_current_thread()
767
+ .enable_all()
768
+ .build()
769
+ .expect("failed to build Tokio runtime")
770
+ .block_on(async move {
771
+ let mut config = server::ServerConfig::new(&host_owned, port);
772
+ config.workers = workers.unwrap_or(0);
773
+
774
+ let server = Server::new(
775
+ config,
776
+ router,
777
+ handlers,
778
+ middleware,
779
+ websocket_handlers,
780
+ dependency_container,
781
+ guards,
782
+ prometheus,
783
+ );
784
+
785
+ // Startup hooks
786
+ for handler in &startup_handlers {
787
+ if let Err(e) = run_lifecycle_handler_async(handler.clone()).await {
788
+ eprintln!("Error in startup handler: {e}");
789
+ }
790
+ }
796
791
 
797
- let _ = server.run().await;
792
+ let _ = server.run().await;
798
793
 
799
- // Shutdown hooks — KeyboardInterrupt is expected on CTRL+C, suppress it.
800
- for handler in &shutdown_handlers {
801
- match run_lifecycle_handler_async(handler.clone()).await {
802
- Err(e) if !e.to_string().contains("KeyboardInterrupt") => {
803
- eprintln!("Error in shutdown handler: {e}");
794
+ // Shutdown hooks
795
+ for handler in &shutdown_handlers {
796
+ match run_lifecycle_handler_async(handler.clone()).await {
797
+ Err(e) if !e.to_string().contains("KeyboardInterrupt") => {
798
+ eprintln!("Error in shutdown handler: {e}");
799
+ }
800
+ _ => {}
801
+ }
804
802
  }
805
- _ => {}
806
- }
807
- }
808
-
809
- Ok(())
810
- })
803
+ })
804
+ });
805
+ Ok(())
811
806
  }
812
807
 
813
808
  /// Internal route registration.
@@ -2112,5 +2107,8 @@ fn _cello(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
2112
2107
  m.add_class::<PyCqrsConfig>()?;
2113
2108
  m.add_class::<PySagaConfig>()?;
2114
2109
 
2110
+ // RFC 7807 error type
2111
+ m.add_class::<error::ProblemDetails>()?;
2112
+
2115
2113
  Ok(())
2116
2114
  }