sqlmodel-ext 0.5.2__tar.gz → 0.5.3__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 (267) hide show
  1. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/CHANGELOG.md +18 -0
  2. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/PKG-INFO +2 -2
  3. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/pyproject.toml +4 -2
  4. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/__init__.py +1 -1
  5. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/cached_table.py +18 -3
  6. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_cached_commit_paths.py +35 -0
  7. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_enhanced_session.py +21 -0
  8. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/.github/workflows/ci.yml +0 -0
  9. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/.gitignore +0 -0
  10. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/LICENSE +0 -0
  11. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/README.md +0 -0
  12. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/README_zh.md +0 -0
  13. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/ai-rules/AGENTS.md +0 -0
  14. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/ai-rules/CLAUDE.md +0 -0
  15. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/ai-rules/README.md +0 -0
  16. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/.vitepress/config.mts +0 -0
  17. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/.vitepress/en.mts +0 -0
  18. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/.vitepress/theme/index.ts +0 -0
  19. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/.vitepress/theme/style/custom.css +0 -0
  20. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/.vitepress/zh.mts +0 -0
  21. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/cached-table.md +0 -0
  22. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/cascade-delete-semantics.md +0 -0
  23. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/crud-pipeline.md +0 -0
  24. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/index.md +0 -0
  25. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/metaclass.md +0 -0
  26. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/optimistic-lock.md +0 -0
  27. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/polymorphic-internals.md +0 -0
  28. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/prerequisites.md +0 -0
  29. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/relation-load-checker.md +0 -0
  30. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/relation-preload.md +0 -0
  31. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/single-source-of-truth.md +0 -0
  32. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/transactional-cache-transparency.md +0 -0
  33. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/explanation/unset-three-state.md +0 -0
  34. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/cached-table.md +0 -0
  35. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/crud.md +0 -0
  36. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/field-types.md +0 -0
  37. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/index.md +0 -0
  38. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/optimistic-lock.md +0 -0
  39. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/pagination.md +0 -0
  40. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/polymorphic.md +0 -0
  41. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/relation-load-checker.md +0 -0
  42. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/guide/relation-preload.md +0 -0
  43. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/aggregate-queries.md +0 -0
  44. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/cache-queries.md +0 -0
  45. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/check-partial-dtos.md +0 -0
  46. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/configure-cascade-delete.md +0 -0
  47. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/define-jti-models.md +0 -0
  48. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/define-sti-models.md +0 -0
  49. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/enforce-locking-and-isolation.md +0 -0
  50. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/extra-mixins.md +0 -0
  51. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/handle-concurrent-updates.md +0 -0
  52. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/handle-referenced-deletes.md +0 -0
  53. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/index.md +0 -0
  54. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/integrate-with-fastapi.md +0 -0
  55. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/keyset-pagination.md +0 -0
  56. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/migrate-to-0-5.md +0 -0
  57. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/paginate-a-list-endpoint.md +0 -0
  58. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/prevent-missing-greenlet.md +0 -0
  59. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/release-connection-during-long-io.md +0 -0
  60. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/type-check-with-basedpyright.md +0 -0
  61. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/use-with-ai-assistants.md +0 -0
  62. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/how-to/write-patch-endpoints.md +0 -0
  63. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/index.md +0 -0
  64. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/cached-table.md +0 -0
  65. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/crud.md +0 -0
  66. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/index.md +0 -0
  67. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/metaclass.md +0 -0
  68. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/optimistic-lock.md +0 -0
  69. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/polymorphic.md +0 -0
  70. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/prerequisites.md +0 -0
  71. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/relation-load-checker.md +0 -0
  72. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/internals/relation-preload.md +0 -0
  73. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/base-classes.md +0 -0
  74. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/crud-methods.md +0 -0
  75. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/decorators.md +0 -0
  76. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/field-types.md +0 -0
  77. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/index.md +0 -0
  78. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/mixins.md +0 -0
  79. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/reference/pagination-types.md +0 -0
  80. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/tutorials/01-getting-started.md +0 -0
  81. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/tutorials/02-building-a-blog-api.md +0 -0
  82. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/tutorials/03-adding-redis-cache.md +0 -0
  83. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/en/tutorials/index.md +0 -0
  84. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/cached-table.md +0 -0
  85. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/cascade-delete-semantics.md +0 -0
  86. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/crud-pipeline.md +0 -0
  87. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/index.md +0 -0
  88. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/metaclass.md +0 -0
  89. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/optimistic-lock.md +0 -0
  90. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/polymorphic-internals.md +0 -0
  91. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/prerequisites.md +0 -0
  92. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/relation-load-checker.md +0 -0
  93. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/relation-preload.md +0 -0
  94. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/single-source-of-truth.md +0 -0
  95. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/transactional-cache-transparency.md +0 -0
  96. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/explanation/unset-three-state.md +0 -0
  97. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/cached-table.md +0 -0
  98. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/crud.md +0 -0
  99. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/field-types.md +0 -0
  100. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/index.md +0 -0
  101. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/optimistic-lock.md +0 -0
  102. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/pagination.md +0 -0
  103. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/polymorphic.md +0 -0
  104. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/relation-load-checker.md +0 -0
  105. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/guide/relation-preload.md +0 -0
  106. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/aggregate-queries.md +0 -0
  107. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/cache-queries.md +0 -0
  108. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/check-partial-dtos.md +0 -0
  109. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/configure-cascade-delete.md +0 -0
  110. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/define-jti-models.md +0 -0
  111. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/define-sti-models.md +0 -0
  112. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/enforce-locking-and-isolation.md +0 -0
  113. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/extra-mixins.md +0 -0
  114. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/handle-concurrent-updates.md +0 -0
  115. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/handle-referenced-deletes.md +0 -0
  116. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/index.md +0 -0
  117. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/integrate-with-fastapi.md +0 -0
  118. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/keyset-pagination.md +0 -0
  119. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/migrate-to-0-5.md +0 -0
  120. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/paginate-a-list-endpoint.md +0 -0
  121. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/prevent-missing-greenlet.md +0 -0
  122. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/release-connection-during-long-io.md +0 -0
  123. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/type-check-with-basedpyright.md +0 -0
  124. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/use-with-ai-assistants.md +0 -0
  125. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/how-to/write-patch-endpoints.md +0 -0
  126. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/index.md +0 -0
  127. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/cached-table.md +0 -0
  128. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/crud.md +0 -0
  129. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/index.md +0 -0
  130. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/metaclass.md +0 -0
  131. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/optimistic-lock.md +0 -0
  132. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/polymorphic.md +0 -0
  133. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/prerequisites.md +0 -0
  134. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/relation-load-checker.md +0 -0
  135. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/internals/relation-preload.md +0 -0
  136. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/base-classes.md +0 -0
  137. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/crud-methods.md +0 -0
  138. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/decorators.md +0 -0
  139. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/field-types.md +0 -0
  140. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/index.md +0 -0
  141. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/mixins.md +0 -0
  142. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/reference/pagination-types.md +0 -0
  143. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/tutorials/01-getting-started.md +0 -0
  144. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/tutorials/02-building-a-blog-api.md +0 -0
  145. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/tutorials/03-adding-redis-cache.md +0 -0
  146. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/docs/tutorials/index.md +0 -0
  147. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/01_unset_patch.py +0 -0
  148. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/02_single_source_types.py +0 -0
  149. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/03_keyset_pagination.py +0 -0
  150. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/04_locking_and_isolation.py +0 -0
  151. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/05_aggregates.py +0 -0
  152. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/06_referenced_delete.py +0 -0
  153. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/07_transactional_cache.py +0 -0
  154. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/08_optimistic_lock.py +0 -0
  155. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/09_relation_preload_bulk.py +0 -0
  156. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/10_typed_select.py +0 -0
  157. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/11_type_errors_caught_by_basedpyright.py +0 -0
  158. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/README.md +0 -0
  159. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/check_derived_demo/pyrightconfig.json +0 -0
  160. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/check_derived_demo/shop/__init__.py +0 -0
  161. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/check_derived_demo/shop/handlers.py +0 -0
  162. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/check_derived_demo/shop/models.py +0 -0
  163. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/examples/consumer_integration/run.py +0 -0
  164. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/package.json +0 -0
  165. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/pnpm-lock.yaml +0 -0
  166. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/pyrightconfig.json +0 -0
  167. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/_compat.py +0 -0
  168. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/_exceptions.py +0 -0
  169. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/_sa_type.py +0 -0
  170. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/_type_unwrap.py +0 -0
  171. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/_utils.py +0 -0
  172. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/base.py +0 -0
  173. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/check_derived.py +0 -0
  174. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/constants.py +0 -0
  175. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/dependencies.py +0 -0
  176. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/derived_decls.py +0 -0
  177. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/__init__.py +0 -0
  178. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/_internal/__init__.py +0 -0
  179. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/_internal/path.py +0 -0
  180. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/_ssrf.py +0 -0
  181. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/dialects/__init__.py +0 -0
  182. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/dialects/postgresql/__init__.py +0 -0
  183. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/dialects/postgresql/array.py +0 -0
  184. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/dialects/postgresql/exceptions.py +0 -0
  185. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/dialects/postgresql/jsonb_types.py +0 -0
  186. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/dialects/postgresql/numpy_vector.py +0 -0
  187. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/ip_address.py +0 -0
  188. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/mixins/__init__.py +0 -0
  189. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/mixins/module_name_mixin.py +0 -0
  190. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/field_types/url.py +0 -0
  191. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/__init__.py +0 -0
  192. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/_uuid.py +0 -0
  193. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/constants.py +0 -0
  194. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/exceptions.py +0 -0
  195. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/info_response.py +0 -0
  196. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/migration_cache_invalidation.py +0 -0
  197. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/mixin_table_scan.py +0 -0
  198. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/optimistic_lock.py +0 -0
  199. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/polymorphic.py +0 -0
  200. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/relation_preload.py +0 -0
  201. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/resource_quota.py +0 -0
  202. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/table.py +0 -0
  203. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/mixins/trgm_searchable.py +0 -0
  204. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/pagination.py +0 -0
  205. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/py.typed +0 -0
  206. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/relation_load_checker.py +0 -0
  207. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/select.py +0 -0
  208. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/session.py +0 -0
  209. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/src/sqlmodel_ext/unset.py +0 -0
  210. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/__init__.py +0 -0
  211. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/_models.py +0 -0
  212. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/conftest.py +0 -0
  213. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_annotated_default_factory_inheritance.py +0 -0
  214. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_array_explicit_field_sa_type.py +0 -0
  215. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_basic_crud.py +0 -0
  216. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_cached_table.py +0 -0
  217. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_check_derived.py +0 -0
  218. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_crud_extended.py +0 -0
  219. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_decimal_types.py +0 -0
  220. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_deferred_index.py +0 -0
  221. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_derived_decls.py +0 -0
  222. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_examples.py +0 -0
  223. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_field_types_aliases_extra.py +0 -0
  224. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_field_types_jsonb_limits.py +0 -0
  225. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_field_types_misc.py +0 -0
  226. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_field_types_str_num.py +0 -0
  227. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_field_types_url_ssrf.py +0 -0
  228. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_051_rereview.py +0 -0
  229. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_051_review.py +0 -0
  230. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_051_review2.py +0 -0
  231. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_check_derived_review2.py +0 -0
  232. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_check_derived_temp_root.py +0 -0
  233. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_nested_duplicate_names.py +0 -0
  234. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_omitted_sentinel_alias_modes.py +0 -0
  235. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_partial_alias_generator.py +0 -0
  236. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_partial_annotated_field_warnings.py +0 -0
  237. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_partial_discriminator.py +0 -0
  238. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_partial_frozen_field.py +0 -0
  239. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_partial_rhs_field_metadata.py +0 -0
  240. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_review4_followup.py +0 -0
  241. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_final_review5.py +0 -0
  242. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_inherited_column_attributes.py +0 -0
  243. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_metaclass_extra.py +0 -0
  244. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_metaclass_field_resolution_052.py +0 -0
  245. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_mixins_extra.py +0 -0
  246. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_optimistic_lock.py +0 -0
  247. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_pagination.py +0 -0
  248. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_partial.py +0 -0
  249. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_pg_dialect_types.py +0 -0
  250. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_polymorphic_jti.py +0 -0
  251. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_polymorphic_sti_query.py +0 -0
  252. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_query_dependency.py +0 -0
  253. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_query_dependency_metadata.py +0 -0
  254. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_relation_load_checker.py +0 -0
  255. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_relation_load_checker_load_resolution.py +0 -0
  256. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_relation_load_checker_sessions.py +0 -0
  257. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_relation_preload.py +0 -0
  258. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_relationship_pep604.py +0 -0
  259. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_review_mixed_version_cache.py +0 -0
  260. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_review_raw_insert_cache.py +0 -0
  261. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_review_raw_insert_edges.py +0 -0
  262. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_review_raw_insert_late_model.py +0 -0
  263. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_review_raw_insert_sti.py +0 -0
  264. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_select.py +0 -0
  265. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_smoke_imports.py +0 -0
  266. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_sti_default_isolation.py +0 -0
  267. {sqlmodel_ext-0.5.2 → sqlmodel_ext-0.5.3}/tests/test_unset.py +0 -0
@@ -7,6 +7,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
7
7
  > there is no stability or backward-compatibility guarantee between versions, and
8
8
  > you use it at your own risk. Pin the exact version you depend on.
9
9
 
10
+ ## [0.5.3]
11
+
12
+ ### Fixed
13
+
14
+ - After `session.invalidate()`, a session that kept being used skipped the Redis cache
15
+ for every read and write on the tables it had written in the invalidated transaction.
16
+ `invalidate()` ends the transaction without dispatching `after_rollback`, so the
17
+ pending-invalidation dict was never cleared, and that dict is also one of the sources
18
+ of the "uncommitted writes" check that makes `get()` bypass the cache. The dict is now
19
+ cleared when the outermost transaction ends, the same place that already cleared the
20
+ flushed-tables set. Commit, rollback, `close()` and `reset()` were not affected. The
21
+ cache contents were always correct; the effect was lost cache hits, not stale data.
22
+ - A fresh `pip install sqlmodel-ext` could not be imported: SQLAlchemy 2.1 no longer
23
+ installs `greenlet` by default, and `sqlmodel_ext` imports `sqlalchemy.ext.asyncio` when
24
+ the package is imported, which raised `ImportError` without it. The dependency is now
25
+ `sqlalchemy[asyncio]`, which installs `greenlet`. Environments that already had
26
+ `greenlet` (or SQLAlchemy 2.0) were not affected. Also present in 0.5.2 and earlier.
27
+
10
28
  ## [0.5.2]
11
29
 
12
30
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sqlmodel-ext
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: Extended SQLModel infrastructure: smart metaclass, async CRUD mixins, polymorphic inheritance, optimistic locking, relation preloading, and reusable field types.
5
5
  Project-URL: Homepage, https://github.com/Foxerine/sqlmodel-ext
6
6
  Project-URL: Repository, https://github.com/Foxerine/sqlmodel-ext
@@ -23,7 +23,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Typing :: Typed
24
24
  Requires-Python: >=3.12
25
25
  Requires-Dist: pydantic>=2.12
26
- Requires-Dist: sqlalchemy>=2.0
26
+ Requires-Dist: sqlalchemy[asyncio]>=2.0
27
27
  Requires-Dist: sqlmodel>=0.0.32
28
28
  Requires-Dist: typing-extensions>=4.14.1
29
29
  Provides-Extra: alembic
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sqlmodel-ext"
7
- version = "0.5.2"
7
+ version = "0.5.3"
8
8
  description = "Extended SQLModel infrastructure: smart metaclass, async CRUD mixins, polymorphic inheritance, optimistic locking, relation preloading, and reusable field types."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -32,7 +32,9 @@ dependencies = [
32
32
  # 2.12 is the first release shipping ``pydantic.experimental.missing_sentinel.MISSING``
33
33
  # (the object ``Unset`` is) and ``Field(exclude_if=...)`` (behind ``EXCLUDE_IF_NONE``).
34
34
  "pydantic>=2.12",
35
- "sqlalchemy>=2.0",
35
+ # The ``asyncio`` extra pulls in ``greenlet``: ``sqlmodel_ext.session`` imports
36
+ # ``sqlalchemy.ext.asyncio`` at module level, and SQLAlchemy 2.1 made greenlet optional.
37
+ "sqlalchemy[asyncio]>=2.0",
36
38
  # ``sqlmodel_ext.unset`` imports ``typing_extensions.Sentinel`` directly (to register
37
39
  # its deepcopy reducer); pydantic 2.12 already requires this floor transitively.
38
40
  "typing-extensions>=4.14.1",
@@ -20,7 +20,7 @@ Quick start::
20
20
  user = await user.save(session)
21
21
  users = await User.get(session, fetch_mode="all")
22
22
  """
23
- __version__ = "0.5.2"
23
+ __version__ = "0.5.3"
24
24
 
25
25
  # Every re-export below uses the redundant ``X as X`` form: the package ships
26
26
  # ``py.typed``, and type checkers treat a plain ``from m import X`` in a typed
@@ -265,7 +265,7 @@ _FORBIDDEN_DIRECT_CALLS: frozenset[str] = frozenset({
265
265
 
266
266
 
267
267
  def _on_outermost_transaction_end(session: _SyncSession, transaction: SessionTransaction) -> None:
268
- """Clear the "tables with uncommitted writes" set when the outermost transaction ends.
268
+ """Clear the state that feeds the "uncommitted writes" check when the outermost transaction ends.
269
269
 
270
270
  ``transaction.parent is None`` is the *common exit* of commit / rollback /
271
271
  ``close()`` / ``reset()`` / ``invalidate()`` -- all of them dispatch this
@@ -276,12 +276,26 @@ def _on_outermost_transaction_end(session: _SyncSession, transaction: SessionTra
276
276
  cleanup always misses an entry point. Nested savepoints and the
277
277
  SUBTRANSACTION of a flush (``parent`` set) are ignored.
278
278
 
279
+ ``_SESSION_PENDING_CACHE_KEY`` is cleared here as well. Besides being the
280
+ list of pending invalidations, it is one of the three sources of
281
+ ``_tables_with_uncommitted_writes``: if it survives an ``invalidate()``,
282
+ every later ``get()`` on those tables skips the cache for as long as the
283
+ session is reused. On commit and on the outermost rollback,
284
+ ``after_commit`` / ``after_rollback`` have already popped it, so this is a
285
+ no-op there; ``invalidate()`` fires only this event.
286
+
287
+ Do **not** clear ``_SESSION_COMMITTED_PENDING_KEY`` here: the enhanced
288
+ ``commit()`` reads it after ``super().commit()`` returns, and this event
289
+ fires inside ``super().commit()``, right after ``after_commit``. Clearing
290
+ it here would drop the synchronous invalidation of every enhanced commit.
291
+
279
292
  Registered at import time (not by ``_register_session_commit_hook``):
280
293
  ``register_raw_dml_write`` fills the set for every session, cached models
281
294
  or not, so its lifetime must not depend on cache configuration.
282
295
  """
283
296
  if transaction.parent is None:
284
297
  session.info.pop(_SESSION_FLUSHED_TABLES, None)
298
+ session.info.pop(_SESSION_PENDING_CACHE_KEY, None)
285
299
 
286
300
 
287
301
  event.listen(_SyncSession, "after_transaction_end", _on_outermost_transaction_end)
@@ -2590,8 +2604,9 @@ class CachedTableBaseMixin(TableBaseMixin):
2590
2604
 
2591
2605
  This is **not** the common exit of transaction termination:
2592
2606
  ``close()`` / ``reset()`` do not dispatch ``after_rollback``, and the
2593
- async ``invalidate()`` bypasses them entirely. Transaction-level state
2594
- (such as ``_SESSION_FLUSHED_TABLES``) is therefore cleared in the
2607
+ async ``invalidate()`` bypasses them entirely. State that feeds the
2608
+ uncommitted-writes check (``_SESSION_FLUSHED_TABLES`` and
2609
+ ``_SESSION_PENDING_CACHE_KEY``) is therefore cleared in the
2595
2610
  always-on ``_on_outermost_transaction_end`` listener -- do not
2596
2611
  add more keys here. The invalidation-tracking keys cleared here are
2597
2612
  normally consumed by ``after_commit`` / cleared by ``after_rollback``
@@ -25,6 +25,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine
25
25
  from sqlmodel.ext.asyncio.session import AsyncSession as PlainAsyncSession
26
26
 
27
27
  from sqlmodel_ext import AsyncSession, CachedTableBaseMixin, SQLModelBase, UUIDTableBaseMixin
28
+ from sqlmodel_ext.mixins.cached_table import _SESSION_COMMITTED_PENDING_KEY
28
29
 
29
30
  FALLBACK_MESSAGE = "fallback compensation triggered"
30
31
 
@@ -102,6 +103,40 @@ async def test_enhanced_commit_invalidates_once_without_fallback_warning(
102
103
  assert await _version(fake_redis) == before + 1, "one commit must invalidate the query cache exactly once"
103
104
 
104
105
 
106
+ @pytest.mark.asyncio
107
+ async def test_enhanced_commit_preserves_handoff_until_transaction_end_listener_returns(
108
+ engine: AsyncEngine,
109
+ fake_redis: fakeredis.aioredis.FakeRedis,
110
+ caplog: pytest.LogCaptureFixture,
111
+ ) -> None:
112
+ """The outermost transaction-end listener must not erase the pending hand-off."""
113
+ gid = await _seed(engine, fake_redis)
114
+ caplog.set_level(logging.WARNING)
115
+ before = await _version(fake_redis)
116
+
117
+ async with AsyncSession(engine) as s:
118
+ observed_handoff: list[set[Any]] = []
119
+
120
+ def _capture_handoff_after_transaction_end(sync_session: Any, transaction: Any) -> None:
121
+ if transaction.parent is not None:
122
+ return
123
+ handed_over = sync_session.info.get(_SESSION_COMMITTED_PENDING_KEY)
124
+ observed_handoff.append(
125
+ set(handed_over.get(CommitPathGadget, set())) if isinstance(handed_over, dict) else set()
126
+ )
127
+
128
+ sa_event.listen(s.sync_session, 'after_transaction_end', _capture_handoff_after_transaction_end)
129
+ gadget = await _load(s, gid)
130
+ gadget.quantity = 2
131
+ await s.commit()
132
+ await _drain()
133
+
134
+ assert observed_handoff == [{gid}]
135
+ assert _fallback_records(caplog) == []
136
+ assert await fake_redis.exists(f"id:CommitPathGadget:{gid}") == 0
137
+ assert await _version(fake_redis) == before + 1, "one commit must invalidate the query cache exactly once"
138
+
139
+
105
140
  @pytest.mark.asyncio
106
141
  async def test_crud_commit_and_begin_block_have_no_fallback_warning(
107
142
  engine: AsyncEngine,
@@ -34,6 +34,7 @@ from sqlmodel_ext.mixins.cached_table import (
34
34
  _SESSION_ENHANCED_COMMIT_KEY,
35
35
  _SESSION_FLUSHED_TABLES,
36
36
  _SESSION_PENDING_CACHE_KEY,
37
+ CachedTableBaseMixin,
37
38
  )
38
39
  from sqlmodel_ext.mixins.table import SESSION_REPEATABLE_READ_KEY
39
40
  from sqlmodel_ext.session import (
@@ -73,6 +74,26 @@ async def test_commit_plain_model_degrades_to_plain_commit(
73
74
  assert rows[0].name == "plain"
74
75
 
75
76
 
77
+ @pytest.mark.asyncio
78
+ @pytest.mark.parametrize('boundary', ['commit', 'rollback', 'close', 'reset', 'invalidate'])
79
+ async def test_transaction_boundary_clears_pending_write_tracking(
80
+ enhanced_session: AsyncSession,
81
+ boundary: str,
82
+ ) -> None:
83
+ """Every outermost transaction boundary must make pending writes invisible to the reused session."""
84
+ CachedTableBaseMixin._register_session_commit_hook()
85
+ enhanced_session.add(SessEntry(name='pending'))
86
+ await enhanced_session.flush()
87
+ enhanced_session.info[_SESSION_PENDING_CACHE_KEY] = {SessEntry: {1}}
88
+ assert CachedTableBaseMixin._tables_with_uncommitted_writes(enhanced_session)
89
+
90
+ boundary_method = getattr(enhanced_session, boundary)
91
+ await boundary_method()
92
+
93
+ assert _SESSION_PENDING_CACHE_KEY not in enhanced_session.info
94
+ assert CachedTableBaseMixin._tables_with_uncommitted_writes(enhanced_session) == set()
95
+
96
+
76
97
  @pytest.mark.asyncio
77
98
  async def test_reset_clears_tracking_state(enhanced_session: AsyncSession) -> None:
78
99
  enhanced_session.info[SESSION_FOR_UPDATE_KEY] = {123}
File without changes
File without changes
File without changes
File without changes
File without changes