ferro-orm 0.12.0__tar.gz → 0.12.1__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 (227) hide show
  1. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/AGENTS.md +20 -1
  2. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/CHANGELOG.md +9 -0
  3. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/Cargo.lock +1 -1
  4. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/Cargo.toml +1 -1
  5. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/PKG-INFO +1 -1
  6. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/migrating-to-v0-12-0.md +4 -0
  7. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/pyproject.toml +1 -1
  8. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/_core.pyi +1 -1
  9. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/models.py +18 -15
  10. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/relations/descriptors.py +3 -4
  11. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/session.py +3 -1
  12. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/operations.rs +3 -2
  13. ferro_orm-0.12.1/tests/test_framework_predicates.py +72 -0
  14. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_session.py +26 -0
  15. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/PERMISSIONS.md +0 -0
  18. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/PYPI_CHECKLIST.md +0 -0
  19. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/PYPI_SETUP.md +0 -0
  20. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/generated/wheels.generated.yml +0 -0
  21. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/pull_request_template.md +0 -0
  22. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/workflows/ci.yml +0 -0
  23. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/workflows/packaging-smoke.yml +0 -0
  24. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/workflows/publish-docs.yml +0 -0
  25. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/workflows/publish.yml +0 -0
  26. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.github/workflows/release.yml +0 -0
  27. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.gitignore +0 -0
  28. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.pre-commit-config.yaml +0 -0
  29. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/.python-version +0 -0
  30. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/CONTRIBUTING.md +0 -0
  31. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/LICENSE +0 -0
  32. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/README.md +0 -0
  33. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/crates/ferro-migrate/Cargo.toml +0 -0
  34. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/crates/ferro-migrate/src/lib.rs +0 -0
  35. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/crates/ferro-schema-ir/Cargo.toml +0 -0
  36. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/crates/ferro-schema-ir/src/lib.rs +0 -0
  37. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
  38. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
  39. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
  40. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
  41. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
  42. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/multiple_databases.py +0 -0
  43. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/mutations.py +0 -0
  44. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/pagination.py +0 -0
  45. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/predicates.py +0 -0
  46. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/predicates_annotated.py +0 -0
  47. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/quickstart.py +0 -0
  48. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/quickstart_annotated.py +0 -0
  49. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/raw_sql.py +0 -0
  50. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/relationships.py +0 -0
  51. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/relationships_annotated.py +0 -0
  52. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/soft_deletes.py +0 -0
  53. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/soft_deletes_annotated.py +0 -0
  54. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/testing_conftest.py +0 -0
  55. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/timestamps.py +0 -0
  56. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/timestamps_annotated.py +0 -0
  57. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/examples/transactions.py +0 -0
  58. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/connection.md +0 -0
  59. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/exceptions.md +0 -0
  60. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/fields.md +0 -0
  61. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/migrations.md +0 -0
  62. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/model.md +0 -0
  63. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/queries.md +0 -0
  64. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/raw-sql.md +0 -0
  65. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/relationships.md +0 -0
  66. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/api/transactions.md +0 -0
  67. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/changelog.md +0 -0
  68. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/concepts/architecture.md +0 -0
  69. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/concepts/backends.md +0 -0
  70. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/concepts/identity-map.md +0 -0
  71. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/concepts/performance.md +0 -0
  72. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/concepts/query-typing.md +0 -0
  73. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/concepts/type-safety.md +0 -0
  74. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/contributing.md +0 -0
  75. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/faq.md +0 -0
  76. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/getting-started/installation.md +0 -0
  77. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/getting-started/next-steps.md +0 -0
  78. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/getting-started/quickstart.md +0 -0
  79. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/connections.md +0 -0
  80. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/migrations.md +0 -0
  81. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/models-and-fields.md +0 -0
  82. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/mutations.md +0 -0
  83. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/queries.md +0 -0
  84. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/raw-sql.md +0 -0
  85. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/relationships.md +0 -0
  86. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/guide/transactions.md +0 -0
  87. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/migrate-from-sqlalchemy.md +0 -0
  88. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/multiple-databases.md +0 -0
  89. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/pagination.md +0 -0
  90. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/soft-deletes.md +0 -0
  91. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/testing.md +0 -0
  92. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/howto/timestamps.md +0 -0
  93. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/index.md +0 -0
  94. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/roadmap.md +0 -0
  95. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/stylesheets/extra.css +0 -0
  96. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/pages/why-ferro.md +0 -0
  97. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
  98. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
  99. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
  100. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +0 -0
  101. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +0 -0
  102. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +0 -0
  103. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
  104. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
  105. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/ir-first-migration-guide.md +0 -0
  106. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/plans/ir-first-release-checklist.md +0 -0
  107. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/rfc/ir-contracts-v1.md +0 -0
  108. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/README.md +0 -0
  109. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
  110. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
  111. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
  112. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +0 -0
  113. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +0 -0
  114. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
  115. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
  116. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
  117. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
  118. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/cross-emitter-ddl-parity.md +0 -0
  119. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
  120. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/foreign-key-index.md +0 -0
  121. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
  122. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/ir-invariants.md +0 -0
  123. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
  124. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
  125. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/docs/solutions/patterns/typed-null-binds.md +0 -0
  126. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/justfile +0 -0
  127. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/scripts/demo_queries.py +0 -0
  128. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/scripts/demo_queries_pre_v012.py +0 -0
  129. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/backend.rs +0 -0
  130. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/codec.rs +0 -0
  131. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/connection.rs +0 -0
  132. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/__init__.py +0 -0
  133. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/_annotation_utils.py +0 -0
  134. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/_deprecations.py +0 -0
  135. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/_shadow_fk_types.py +0 -0
  136. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/base.py +0 -0
  137. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/composite_indexes.py +0 -0
  138. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/composite_uniques.py +0 -0
  139. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/exceptions.py +0 -0
  140. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/fields.py +0 -0
  141. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/ir/__init__.py +0 -0
  142. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/ir/compiler.py +0 -0
  143. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/metaclass.py +0 -0
  144. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/migrations/__init__.py +0 -0
  145. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/migrations/alembic.py +0 -0
  146. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/py.typed +0 -0
  147. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/query/__init__.py +0 -0
  148. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/query/builder.py +0 -0
  149. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/query/nodes.py +0 -0
  150. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/raw.py +0 -0
  151. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/relations/__init__.py +0 -0
  152. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/schema_metadata.py +0 -0
  153. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/ferro/state.py +0 -0
  154. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/hydration.rs +0 -0
  155. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/introspect.rs +0 -0
  156. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/lib.rs +0 -0
  157. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/migrate.rs +0 -0
  158. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/query.rs +0 -0
  159. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/schema.rs +0 -0
  160. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/schema_bind.rs +0 -0
  161. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/src/state.rs +0 -0
  162. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/__init__.py +0 -0
  163. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/conftest.py +0 -0
  164. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/db_backends.py +0 -0
  165. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/ir_vectors/README.md +0 -0
  166. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
  167. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/ir_vectors/query_user_compound_v1.json +0 -0
  168. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
  169. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +0 -0
  170. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/shadow_reports/postgres.json +0 -0
  171. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/fixtures/shadow_reports/sqlite.json +0 -0
  172. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_aggregation.py +0 -0
  173. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_alembic_autogenerate.py +0 -0
  174. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_alembic_bridge.py +0 -0
  175. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_alembic_db_type.py +0 -0
  176. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_alembic_nullability.py +0 -0
  177. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_alembic_type_mapping.py +0 -0
  178. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_auto_migrate.py +0 -0
  179. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_bulk_update.py +0 -0
  180. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_composite_index.py +0 -0
  181. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_composite_unique.py +0 -0
  182. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_connection.py +0 -0
  183. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_connection_redaction.py +0 -0
  184. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_constraints.py +0 -0
  185. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_cross_emitter_parity.py +0 -0
  186. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_crud.py +0 -0
  187. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_db_backends.py +0 -0
  188. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_db_type_cross_emitter_parity.py +0 -0
  189. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_db_type_integration.py +0 -0
  190. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_db_type_typing.py +0 -0
  191. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_db_type_validation.py +0 -0
  192. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_deletion.py +0 -0
  193. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_deprecated_operator_inventory.py +0 -0
  194. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_deprecations.py +0 -0
  195. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_docs_examples.py +0 -0
  196. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_documentation_features.py +0 -0
  197. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_enum_cold_hydration.py +0 -0
  198. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_field_wrapper.py +0 -0
  199. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_helpers.py +0 -0
  200. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_hydration.py +0 -0
  201. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_ir_vectors_contract.py +0 -0
  202. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_metaclass_internals.py +0 -0
  203. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_metadata.py +0 -0
  204. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_migrate_plan.py +0 -0
  205. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_models.py +0 -0
  206. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_named_connections_integration.py +0 -0
  207. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_one_to_one.py +0 -0
  208. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_query_builder.py +0 -0
  209. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_query_typing.py +0 -0
  210. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_raw_sql.py +0 -0
  211. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_refresh.py +0 -0
  212. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_relationship_engine.py +0 -0
  213. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_schema.py +0 -0
  214. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_schema_constraints.py +0 -0
  215. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_schema_db_type_metadata.py +0 -0
  216. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_schema_enum_annotations.py +0 -0
  217. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_shadow_fk_types.py +0 -0
  218. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_shadow_reports.py +0 -0
  219. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_sqlite_alembic_reconnect_hydration.py +0 -0
  220. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_static_contracts.py +0 -0
  221. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_string_search.py +0 -0
  222. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_structural_types.py +0 -0
  223. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_temporal_types.py +0 -0
  224. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_transactions.py +0 -0
  225. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/tests/test_typed_null_binds.py +0 -0
  226. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/uv.lock +0 -0
  227. {ferro_orm-0.12.0 → ferro_orm-0.12.1}/zensical.toml +0 -0
@@ -90,7 +90,8 @@ If you add a new schema feature (e.g. partial indexes, exclusion constraints):
90
90
  numbered list above.
91
91
  2. Implement it in **both** the Alembic and Rust paths in the same PR.
92
92
  3. Add a parity test that asserts the names match.
93
- 4. Add a regression entry to `CHANGELOG.md`.
93
+ 4. Do not edit `CHANGELOG.md` manually — release tooling records entries at
94
+ release time (see I-11).
94
95
 
95
96
  ---
96
97
 
@@ -276,3 +277,21 @@ Rules:
276
277
  updates are complete before merge.
277
278
  - AI agents and human contributors follow the same requirement. If issue status
278
279
  closure is missing, the PR is not done.
280
+
281
+ ---
282
+
283
+ ## I-11: Do not edit CHANGELOG.md manually
284
+
285
+ `CHANGELOG.md` is updated automatically by the release workflow (semantic
286
+ release / release tooling). Agents and contributors must **not** add,
287
+ reorder, or edit changelog entries in feature, bugfix, or docs PRs.
288
+
289
+ What to do instead:
290
+
291
+ - Describe user-visible changes in the PR title and body.
292
+ - Rely on conventional commit messages and the release process to populate
293
+ `CHANGELOG.md` after merge.
294
+
295
+ If release tooling fails to capture a change, fix the release configuration or
296
+ commit message conventions — do not patch `CHANGELOG.md` by hand in ordinary
297
+ PRs.
@@ -1,6 +1,15 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.12.1 (2026-06-23)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Stop framework predicate warnings and bind unnamed sessions to default
9
+ ([#124](https://github.com/syn54x/ferro-orm/pull/124),
10
+ [`66b92d8`](https://github.com/syn54x/ferro-orm/commit/66b92d8b691a1ad806691b9841bcbaf93c78f691))
11
+
12
+
4
13
  ## v0.12.0 (2026-06-23)
5
14
 
6
15
  ### Documentation
@@ -288,7 +288,7 @@ dependencies = [
288
288
 
289
289
  [[package]]
290
290
  name = "ferro"
291
- version = "0.12.0"
291
+ version = "0.12.1"
292
292
  dependencies = [
293
293
  "dashmap",
294
294
  "ferro-migrate",
@@ -3,7 +3,7 @@ members = ["crates/ferro-schema-ir", "crates/ferro-migrate"]
3
3
 
4
4
  [package]
5
5
  name = "ferro"
6
- version = "0.12.0"
6
+ version = "0.12.1"
7
7
  edition = "2024"
8
8
  readme = "README.md"
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ferro-orm
3
- Version: 0.12.0
3
+ Version: 0.12.1
4
4
  Requires-Dist: pydantic>=2.0
5
5
  Requires-Dist: alembic>=1.18.1 ; extra == 'alembic'
6
6
  Requires-Dist: sqlalchemy>=2.0.46 ; extra == 'alembic'
@@ -72,6 +72,10 @@ active session relied on implicit default-connection routing. That fallback is
72
72
  deprecated. Wrap request- or task-scoped work in a session so routing, the
73
73
  identity map, and transactions are explicit and isolated under concurrency.
74
74
 
75
+ After a single unnamed `connect(dsn)`, `async with ferro.engines.session()` (no
76
+ argument) binds to that default connection — you do not need to pass
77
+ `"default"` explicitly.
78
+
75
79
  === "Before (deprecated)"
76
80
 
77
81
  ```python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ferro-orm"
3
- version = "0.12.0"
3
+ version = "0.12.1"
4
4
  description = "A high-performance, Rust-backed ORM for Python."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -160,7 +160,7 @@ async def begin_transaction(
160
160
  async def commit_transaction(tx_id: str, session_id: Optional[str] = None) -> None: ...
161
161
  def transaction_connection_name(tx_id: str, session_id: Optional[str] = None) -> str: ...
162
162
  async def rollback_transaction(tx_id: str, session_id: Optional[str] = None) -> None: ...
163
- def open_session(using: Optional[str] = None) -> str: ...
163
+ def open_session(using: Optional[str] = None) -> tuple[str, str]: ...
164
164
  def close_session(session_id: str) -> None: ...
165
165
  async def raw_execute(
166
166
  sql: str,
@@ -31,7 +31,7 @@ from ._core import (
31
31
  from .base import ForeignKey, foreign_key_allows_none
32
32
  from .exceptions import ModelDoesNotExist
33
33
  from .metaclass import ModelMetaclass
34
- from .query import Query, QueryNode
34
+ from .query import Predicate, Query, QueryNode
35
35
  from .state import (
36
36
  _CURRENT_TRANSACTION,
37
37
  _CURRENT_TRANSACTION_CONNECTION,
@@ -43,6 +43,11 @@ from .state import (
43
43
  _FERRO_CONNECTION_ATTR = "__ferro_connection_name"
44
44
 
45
45
 
46
+ def _field_eq(field_name: str, value: Any) -> Predicate[Any]:
47
+ """Build a lambda predicate for dynamic field equality without operator style."""
48
+ return lambda t, f=field_name, v=value: getattr(t, f) == v
49
+
50
+
46
51
  def _transaction_or_using(
47
52
  using: str | None, session: "Session | None"
48
53
  ) -> tuple[str | None, str | None, str | None]:
@@ -289,12 +294,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
289
294
 
290
295
  if pk_val is not None:
291
296
  name = self.__class__.__name__
292
- query = self.__class__.where(
293
- getattr(self.__class__, pk_field_name) == pk_val
294
- )
297
+ query = self.__class__.where(_field_eq(pk_field_name, pk_val))
295
298
  if operation_using is not None:
296
299
  query = Query(self.__class__, using=operation_using).where(
297
- getattr(self.__class__, pk_field_name) == pk_val
300
+ _field_eq(pk_field_name, pk_val)
298
301
  )
299
302
  await query.delete()
300
303
  evict_instance(
@@ -391,7 +394,9 @@ class Model(BaseModel, metaclass=ModelMetaclass):
391
394
  if pk_field_name is None:
392
395
  raise RuntimeError(f"Model {cls.__name__} does not define a primary key")
393
396
 
394
- instance = await cls.where(getattr(cls, pk_field_name) == pk, session=session).first()
397
+ instance = await cls.where(
398
+ _field_eq(pk_field_name, pk), session=session
399
+ ).first()
395
400
  if instance:
396
401
  cls._fix_types(instance)
397
402
  return instance
@@ -423,10 +428,10 @@ class Model(BaseModel, metaclass=ModelMetaclass):
423
428
  )
424
429
 
425
430
  evict_instance(name, str(pk_val), identity_using, session_id=session_id)
426
- query = self.__class__.where(getattr(self.__class__, pk_field_name) == pk_val)
431
+ query = self.__class__.where(_field_eq(pk_field_name, pk_val))
427
432
  if operation_using is not None:
428
433
  query = Query(self.__class__, using=operation_using).where(
429
- getattr(self.__class__, pk_field_name) == pk_val
434
+ _field_eq(pk_field_name, pk_val)
430
435
  )
431
436
  fresh_instance = await query.first()
432
437
 
@@ -576,7 +581,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
576
581
  """
577
582
  query = Query(cls, session=session)
578
583
  for key, val in fields.items():
579
- query = query.where(getattr(cls, key) == val)
584
+ query = query.where(_field_eq(key, val))
580
585
 
581
586
  instance = await query.first()
582
587
  if instance:
@@ -604,7 +609,7 @@ class Model(BaseModel, metaclass=ModelMetaclass):
604
609
  """
605
610
  query = Query(cls, session=session)
606
611
  for key, val in fields.items():
607
- query = query.where(getattr(cls, key) == val)
612
+ query = query.where(_field_eq(key, val))
608
613
 
609
614
  instance = await query.first()
610
615
  if instance:
@@ -662,9 +667,7 @@ class ModelConnection[M: Model]:
662
667
  f"Model {self.model_cls.__name__} does not define a primary key"
663
668
  )
664
669
 
665
- instance = await self.where(
666
- getattr(self.model_cls, pk_field_name) == pk
667
- ).first()
670
+ instance = await self.where(_field_eq(pk_field_name, pk)).first()
668
671
  if instance:
669
672
  self.model_cls._fix_types(instance)
670
673
  return instance
@@ -677,7 +680,7 @@ class ModelConnection[M: Model]:
677
680
  ) -> tuple[M, bool]:
678
681
  query = Query(self.model_cls, using=self._connection_name)
679
682
  for key, val in fields.items():
680
- query = query.where(getattr(self.model_cls, key) == val)
683
+ query = query.where(_field_eq(key, val))
681
684
 
682
685
  instance = await query.first()
683
686
  if instance:
@@ -691,7 +694,7 @@ class ModelConnection[M: Model]:
691
694
  ) -> tuple[M, bool]:
692
695
  query = Query(self.model_cls, using=self._connection_name)
693
696
  for key, val in fields.items():
694
- query = query.where(getattr(self.model_cls, key) == val)
697
+ query = query.where(_field_eq(key, val))
695
698
 
696
699
  instance = await query.first()
697
700
  if instance:
@@ -69,9 +69,10 @@ class RelationshipDescriptor(BaseModel):
69
69
 
70
70
  pk_val = getattr(instance, pk_field)
71
71
 
72
+ fk_field = f"{self.field_name}_id"
72
73
  if self.is_one_to_one:
73
74
  return self._target_model.where(
74
- getattr(self._target_model, f"{self.field_name}_id") == pk_val
75
+ lambda t, f=fk_field, v=pk_val: getattr(t, f) == v
75
76
  ).first()
76
77
 
77
78
  from ..query.builder import Relation
@@ -79,9 +80,7 @@ class RelationshipDescriptor(BaseModel):
79
80
  return Relation(
80
81
  self._target_model,
81
82
  using=_instance_origin_outside_transaction(instance),
82
- ).where(
83
- getattr(self._target_model, f"{self.field_name}_id") == pk_val
84
- )
83
+ ).where(lambda t, f=fk_field, v=pk_val: getattr(t, f) == v)
85
84
 
86
85
 
87
86
  class ForwardDescriptor(BaseModel):
@@ -17,7 +17,9 @@ class Session:
17
17
  _token: Any = None
18
18
 
19
19
  async def __aenter__(self) -> "Session":
20
- self.session_id = _core_open_session(self.connection_name)
20
+ self.session_id, resolved_name = _core_open_session(self.connection_name)
21
+ if self.connection_name is None:
22
+ self.connection_name = resolved_name
21
23
  self._token = _CURRENT_SESSION.set(self)
22
24
  return self
23
25
 
@@ -825,9 +825,10 @@ pub fn rollback_transaction(
825
825
 
826
826
  #[pyfunction]
827
827
  #[pyo3(signature = (using=None))]
828
- pub fn open_session(using: Option<String>) -> PyResult<String> {
828
+ pub fn open_session(using: Option<String>) -> PyResult<(String, String)> {
829
829
  let (connection_name, _) = active_connection_for_route(using)?;
830
- Ok(register_session(connection_name))
830
+ let session_id = register_session(connection_name.clone());
831
+ Ok((session_id, connection_name))
831
832
  }
832
833
 
833
834
  #[pyfunction]
@@ -0,0 +1,72 @@
1
+ """Regression tests for framework-internal predicate style (issue #121)."""
2
+
3
+ import warnings
4
+ from typing import Annotated
5
+
6
+ import pytest
7
+
8
+ import ferro
9
+
10
+
11
+ pytestmark = pytest.mark.sqlite_only
12
+
13
+
14
+ class FrameworkPredicateMarker(ferro.Model):
15
+ id: Annotated[int | None, ferro.FerroField(primary_key=True)] = None
16
+ name: str
17
+
18
+
19
+ @pytest.fixture(autouse=True)
20
+ def _ensure_models_registered():
21
+ from ferro.state import _MODEL_REGISTRY_PY
22
+
23
+ FrameworkPredicateMarker._reregister_ferro()
24
+ _MODEL_REGISTRY_PY[FrameworkPredicateMarker.__name__] = FrameworkPredicateMarker
25
+ yield
26
+
27
+
28
+ def _operator_predicate_warnings(caught: list[warnings.WarningMessage]) -> list[str]:
29
+ return [
30
+ str(w.message)
31
+ for w in caught
32
+ if issubclass(w.category, DeprecationWarning)
33
+ and "Operator predicate style" in str(w.message)
34
+ ]
35
+
36
+
37
+ @pytest.mark.asyncio
38
+ async def test_framework_helpers_do_not_emit_operator_predicate_warnings(tmp_path):
39
+ db = tmp_path / "framework_predicates.db"
40
+ await ferro.connect(f"sqlite:{db}?mode=rwc", auto_migrate=True)
41
+
42
+ async with ferro.engines.session("default"):
43
+ widget = FrameworkPredicateMarker(id=1, name="demo")
44
+ await widget.save()
45
+
46
+ with warnings.catch_warnings(record=True) as caught:
47
+ warnings.simplefilter("always", DeprecationWarning)
48
+ loaded = await FrameworkPredicateMarker.get_or_none(1)
49
+ assert loaded is not None
50
+ await loaded.refresh()
51
+ found, created = await FrameworkPredicateMarker.get_or_create(name="demo")
52
+ assert found is not None
53
+ assert created is False
54
+ await loaded.delete()
55
+
56
+ assert _operator_predicate_warnings(caught) == []
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_model_connection_get_or_none_does_not_emit_operator_warnings(tmp_path):
61
+ db = tmp_path / "framework_predicates_bound.db"
62
+ await ferro.connect(f"sqlite:{db}?mode=rwc", auto_migrate=True)
63
+
64
+ async with ferro.engines.session("default"):
65
+ await FrameworkPredicateMarker.create(id=2, name="bound")
66
+
67
+ with warnings.catch_warnings(record=True) as caught:
68
+ warnings.simplefilter("always", DeprecationWarning)
69
+ loaded = await FrameworkPredicateMarker.using("default").get_or_none(2)
70
+
71
+ assert loaded is not None
72
+ assert _operator_predicate_warnings(caught) == []
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from typing import Annotated
2
3
 
3
4
  import pytest
@@ -85,6 +86,31 @@ async def test_explicit_session_override_beats_ambient(tmp_path):
85
86
  assert [row.label for row in rows] == ["app"]
86
87
 
87
88
 
89
+ @pytest.mark.asyncio
90
+ async def test_unnamed_session_binds_default_connection(tmp_path):
91
+ app_db = tmp_path / "app.db"
92
+ await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
93
+
94
+ async with ferro.engines.session() as session:
95
+ assert session.connection_name == "default"
96
+ with warnings.catch_warnings(record=True) as caught:
97
+ warnings.simplefilter("always", DeprecationWarning)
98
+ exists = await SessionMarker.where(
99
+ lambda t: t.label == "missing"
100
+ ).exists()
101
+ created = await SessionMarker.create(id=20, label="session-bound")
102
+
103
+ session_warnings = [
104
+ w
105
+ for w in caught
106
+ if issubclass(w.category, DeprecationWarning)
107
+ and "without an active session" in str(w.message)
108
+ ]
109
+ assert not session_warnings
110
+ assert exists is False
111
+ assert created.id == 20
112
+
113
+
88
114
  @pytest.mark.asyncio
89
115
  async def test_legacy_unqualified_operation_warns(tmp_path):
90
116
  app_db = tmp_path / "app.db"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes