ferro-orm 0.12.2__tar.gz → 0.12.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.
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/CHANGELOG.md +13 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/Cargo.lock +1 -1
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/Cargo.toml +1 -1
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/PKG-INFO +1 -1
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/connections.md +2 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/transactions.md +2 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/pyproject.toml +1 -1
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/session.py +22 -16
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/operations.rs +3 -2
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/state.rs +64 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_session.py +168 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/workflows/ci.yml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.github/workflows/release.yml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.gitignore +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/.python-version +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/AGENTS.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/LICENSE +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/README.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/crates/ferro-migrate/Cargo.toml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/crates/ferro-migrate/src/lib.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/crates/ferro-schema-ir/Cargo.toml +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/crates/ferro-schema-ir/src/lib.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/multiple_databases.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/mutations.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/pagination.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/predicates.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/predicates_annotated.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/quickstart.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/quickstart_annotated.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/raw_sql.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/relationships.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/relationships_annotated.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/soft_deletes.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/soft_deletes_annotated.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/testing_conftest.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/timestamps.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/timestamps_annotated.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/examples/transactions.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/connection.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/exceptions.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/fields.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/migrations.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/model.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/queries.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/raw-sql.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/relationships.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/api/transactions.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/changelog.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/concepts/architecture.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/concepts/backends.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/concepts/identity-map.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/concepts/performance.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/concepts/query-typing.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/concepts/type-safety.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/contributing.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/faq.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/getting-started/installation.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/getting-started/next-steps.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/getting-started/quickstart.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/migrations.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/models-and-fields.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/mutations.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/queries.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/raw-sql.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/guide/relationships.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/migrate-from-sqlalchemy.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/migrating-to-v0-12-0.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/multiple-databases.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/pagination.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/soft-deletes.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/testing.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/howto/timestamps.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/index.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/roadmap.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/stylesheets/extra.css +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/pages/why-ferro.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/ir-first-migration-guide.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/ir-first-release-checklist.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/rfc/ir-contracts-v1.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/README.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/cross-emitter-ddl-parity.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/foreign-key-index.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/ir-invariants.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/solutions/patterns/typed-null-binds.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/justfile +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/scripts/demo_queries_pre_v012.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/backend.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/codec.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/connection.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/__init__.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/_core.pyi +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/_deprecations.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/_shadow_fk_types.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/base.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/composite_indexes.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/exceptions.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/fields.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/ir/__init__.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/ir/compiler.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/metaclass.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/migrations/alembic.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/models.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/py.typed +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/query/__init__.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/query/builder.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/query/nodes.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/raw.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/relations/__init__.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/relations/descriptors.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/schema_metadata.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/ferro/state.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/hydration.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/introspect.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/lib.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/migrate.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/query.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/schema.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/src/schema_bind.rs +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/__init__.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/conftest.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/db_backends.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/ir_vectors/README.md +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/ir_vectors/query_user_compound_v1.json +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/shadow_reports/postgres.json +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/fixtures/shadow_reports/sqlite.json +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_aggregation.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_alembic_bridge.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_alembic_db_type.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_alembic_nullability.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_auto_migrate.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_bulk_update.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_composite_index.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_composite_unique.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_connection.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_connection_redaction.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_constraints.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_cross_emitter_parity.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_crud.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_db_backends.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_db_type_cross_emitter_parity.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_db_type_integration.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_db_type_typing.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_db_type_validation.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_deletion.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_deprecated_operator_inventory.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_deprecations.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_documentation_features.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_enum_cold_hydration.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_field_wrapper.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_framework_predicates.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_helpers.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_hydration.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_ir_vectors_contract.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_metadata.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_migrate_plan.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_models.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_named_connections_integration.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_one_to_one.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_query_builder.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_query_typing.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_raw_sql.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_refresh.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_relationship_engine.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_schema.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_schema_constraints.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_schema_db_type_metadata.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_schema_enum_annotations.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_shadow_fk_types.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_shadow_reports.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_sqlite_alembic_reconnect_hydration.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_static_contracts.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_string_search.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_structural_types.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_temporal_types.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_transactions.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/tests/test_typed_null_binds.py +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/uv.lock +0 -0
- {ferro_orm-0.12.2 → ferro_orm-0.12.3}/zensical.toml +0 -0
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.12.3 (2026-06-24)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **session**: Reject close while transactions are active
|
|
9
|
+
([#128](https://github.com/syn54x/ferro-orm/pull/128),
|
|
10
|
+
[`e46f183`](https://github.com/syn54x/ferro-orm/commit/e46f183fba7a3bcbc635f7e66502a77aeb56861d))
|
|
11
|
+
|
|
12
|
+
- **session**: Serialize concurrent close teardown
|
|
13
|
+
([#129](https://github.com/syn54x/ferro-orm/pull/129),
|
|
14
|
+
[`7afa616`](https://github.com/syn54x/ferro-orm/commit/7afa6168abd2eb19b564fc1c2ab9956e03a20fe9))
|
|
15
|
+
|
|
16
|
+
|
|
4
17
|
## v0.12.2 (2026-06-23)
|
|
5
18
|
|
|
6
19
|
### Bug Fixes
|
|
@@ -139,6 +139,8 @@ class App:
|
|
|
139
139
|
|
|
140
140
|
`Session.close()` is safe across asyncio contexts and idempotent. Prefer explicit `session=` routing when ambient context may be unavailable in the closing task.
|
|
141
141
|
|
|
142
|
+
Close raises `RuntimeError` if session-scoped transactions are still open — exit all `transaction()` blocks before closing the session.
|
|
143
|
+
|
|
142
144
|
After a cross-context close, the enter task may still hold a stale ambient session object until a new session is opened; ambient operations then raise `RuntimeError` with a session-closed message (including when `using=` names another connection). If nested sessions are open, close inner sessions from the same task or via cross-context `close()` before closing the outer handle in the original task.
|
|
143
145
|
|
|
144
146
|
Operations on a closed session handle (ambient or explicit `session=`) raise `RuntimeError` rather than falling back to legacy default routing.
|
|
@@ -40,6 +40,8 @@ async with ferro.engines.session("analytics"):
|
|
|
40
40
|
|
|
41
41
|
You can also pass an explicit session handle (`transaction(session=my_session)`) when ambient context is different and you need a deterministic override.
|
|
42
42
|
|
|
43
|
+
`Session.close()` raises `RuntimeError` while session-scoped transactions are still open. Exit all `transaction()` blocks before closing the session.
|
|
44
|
+
|
|
43
45
|
## Raw SQL Inside a Transaction
|
|
44
46
|
|
|
45
47
|
`transaction()` yields a `Transaction` handle exposing `execute` / `fetch_all` / `fetch_one` for raw SQL on the transaction's own connection — useful for Postgres GUCs (`set_config`, `SET LOCAL`), advisory locks, or any one-off statement that doesn't fit a model:
|
|
@@ -28,6 +28,7 @@ class Session:
|
|
|
28
28
|
_enter_task: asyncio.Task[Any] | None = field(
|
|
29
29
|
default=None, repr=False, compare=False
|
|
30
30
|
)
|
|
31
|
+
_close_lock: asyncio.Lock | None = field(default=None, repr=False, compare=False)
|
|
31
32
|
|
|
32
33
|
async def __aenter__(self) -> "Session":
|
|
33
34
|
self.session_id, resolved_name = _core_open_session(self.connection_name)
|
|
@@ -56,28 +57,33 @@ class Session:
|
|
|
56
57
|
|
|
57
58
|
Raises:
|
|
58
59
|
RuntimeError: If the ambient session in this asyncio context does not
|
|
59
|
-
match this handle (same-context lifecycle misuse)
|
|
60
|
+
match this handle (same-context lifecycle misuse), or if
|
|
61
|
+
session-scoped transactions are still open.
|
|
60
62
|
"""
|
|
61
|
-
if self.
|
|
62
|
-
|
|
63
|
+
if self._close_lock is None:
|
|
64
|
+
self._close_lock = asyncio.Lock()
|
|
65
|
+
|
|
66
|
+
async with self._close_lock:
|
|
67
|
+
if self.session_id is None and self._token is None:
|
|
68
|
+
return
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
self._assert_close_allowed()
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
if self.session_id is not None:
|
|
73
|
+
session_id = self.session_id
|
|
74
|
+
_core_close_session(session_id)
|
|
75
|
+
self.session_id = None
|
|
70
76
|
|
|
71
|
-
|
|
77
|
+
if self._token is None:
|
|
78
|
+
self._enter_context = None
|
|
79
|
+
self._enter_task = None
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
token = self._token
|
|
83
|
+
self._token = None
|
|
72
84
|
self._enter_context = None
|
|
73
85
|
self._enter_task = None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
token = self._token
|
|
77
|
-
self._token = None
|
|
78
|
-
self._enter_context = None
|
|
79
|
-
self._enter_task = None
|
|
80
|
-
self._restore_ambient_session(token)
|
|
86
|
+
self._restore_ambient_session(token)
|
|
81
87
|
|
|
82
88
|
def _assert_close_allowed(self) -> None:
|
|
83
89
|
if self._token is None:
|
|
@@ -9,8 +9,8 @@ use crate::backend::{
|
|
|
9
9
|
use crate::query::{QueryDef, query_def_from_ir_payload};
|
|
10
10
|
use crate::state::{
|
|
11
11
|
IDENTITY_MAP, MODEL_REGISTRY, SqlDialect, TRANSACTION_REGISTRY,
|
|
12
|
-
TransactionConnection, TransactionHandle, connection_for_route,
|
|
13
|
-
register_session, session_state, unregister_session,
|
|
12
|
+
TransactionConnection, TransactionHandle, connection_for_route, ensure_session_idle_for_close,
|
|
13
|
+
engine_for_connection, register_session, session_state, unregister_session,
|
|
14
14
|
};
|
|
15
15
|
use ferro_schema_ir::{IrEnvelope, QueryIrPayload};
|
|
16
16
|
use pyo3::prelude::*;
|
|
@@ -833,6 +833,7 @@ pub fn open_session(using: Option<String>) -> PyResult<(String, String)> {
|
|
|
833
833
|
|
|
834
834
|
#[pyfunction]
|
|
835
835
|
pub fn close_session(session_id: String) -> PyResult<()> {
|
|
836
|
+
ensure_session_idle_for_close(&session_id)?;
|
|
836
837
|
if !unregister_session(&session_id) {
|
|
837
838
|
return Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
|
|
838
839
|
"Session '{}' is not active",
|
|
@@ -199,6 +199,20 @@ pub fn session_state(session_id: &str) -> PyResult<Arc<SessionState>> {
|
|
|
199
199
|
})
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
pub(crate) const SESSION_CLOSE_ACTIVE_TRANSACTIONS_MSG: &str =
|
|
203
|
+
"Cannot close session while transactions are active. \
|
|
204
|
+
Exit all transaction() blocks before closing the session.";
|
|
205
|
+
|
|
206
|
+
pub fn ensure_session_idle_for_close(session_id: &str) -> PyResult<()> {
|
|
207
|
+
let session = session_state(session_id)?;
|
|
208
|
+
if !session.transaction_registry.is_empty() {
|
|
209
|
+
return Err(pyo3::exceptions::PyRuntimeError::new_err(
|
|
210
|
+
SESSION_CLOSE_ACTIVE_TRANSACTIONS_MSG,
|
|
211
|
+
));
|
|
212
|
+
}
|
|
213
|
+
Ok(())
|
|
214
|
+
}
|
|
215
|
+
|
|
202
216
|
/// A Rust-native representation of database values.
|
|
203
217
|
///
|
|
204
218
|
/// Used during the GIL-free parsing phase to move data from the database
|
|
@@ -262,3 +276,53 @@ impl RustValue {
|
|
|
262
276
|
}
|
|
263
277
|
}
|
|
264
278
|
}
|
|
279
|
+
|
|
280
|
+
#[cfg(test)]
|
|
281
|
+
mod session_close_tests {
|
|
282
|
+
use super::{TransactionHandle, register_session, unregister_session, SESSION_REGISTRY};
|
|
283
|
+
use crate::backend::EngineHandle;
|
|
284
|
+
use sqlx::sqlite::SqlitePoolOptions;
|
|
285
|
+
|
|
286
|
+
#[tokio::test]
|
|
287
|
+
async fn close_guard_uses_transaction_registry_emptiness() {
|
|
288
|
+
let session_id = register_session("default".to_string());
|
|
289
|
+
|
|
290
|
+
let pool = SqlitePoolOptions::new()
|
|
291
|
+
.max_connections(1)
|
|
292
|
+
.connect("sqlite::memory:")
|
|
293
|
+
.await
|
|
294
|
+
.expect("sqlite memory pool");
|
|
295
|
+
let engine = EngineHandle::new_sqlite(pool);
|
|
296
|
+
let conn = engine
|
|
297
|
+
.begin_transaction_connection()
|
|
298
|
+
.await
|
|
299
|
+
.expect("begin transaction connection");
|
|
300
|
+
|
|
301
|
+
{
|
|
302
|
+
let session = SESSION_REGISTRY
|
|
303
|
+
.get(&session_id)
|
|
304
|
+
.expect("session registered")
|
|
305
|
+
.value()
|
|
306
|
+
.clone();
|
|
307
|
+
session.transaction_registry.insert(
|
|
308
|
+
"tx-test".to_string(),
|
|
309
|
+
TransactionHandle::root(conn, "default".to_string()),
|
|
310
|
+
);
|
|
311
|
+
assert!(!session.transaction_registry.is_empty());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
SESSION_REGISTRY
|
|
315
|
+
.get(&session_id)
|
|
316
|
+
.expect("session registered")
|
|
317
|
+
.transaction_registry
|
|
318
|
+
.clear();
|
|
319
|
+
assert!(
|
|
320
|
+
SESSION_REGISTRY
|
|
321
|
+
.get(&session_id)
|
|
322
|
+
.expect("session registered")
|
|
323
|
+
.transaction_registry
|
|
324
|
+
.is_empty()
|
|
325
|
+
);
|
|
326
|
+
assert!(unregister_session(&session_id));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import re
|
|
2
3
|
import warnings
|
|
3
4
|
from contextlib import AsyncExitStack
|
|
4
5
|
from typing import Annotated
|
|
@@ -182,6 +183,35 @@ async def test_session_close_is_idempotent(tmp_path):
|
|
|
182
183
|
assert session.session_id is None
|
|
183
184
|
|
|
184
185
|
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_concurrent_close_same_loop_is_idempotent(tmp_path):
|
|
188
|
+
app_db = tmp_path / "app.db"
|
|
189
|
+
await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
|
|
190
|
+
|
|
191
|
+
session = ferro.engines.session("default")
|
|
192
|
+
await session.__aenter__()
|
|
193
|
+
await SessionMarker.create(id=1, label="demo")
|
|
194
|
+
|
|
195
|
+
barrier = asyncio.Barrier(2)
|
|
196
|
+
|
|
197
|
+
async def close_after_barrier() -> None:
|
|
198
|
+
await barrier.wait()
|
|
199
|
+
await session.close()
|
|
200
|
+
|
|
201
|
+
results = await asyncio.gather(
|
|
202
|
+
close_after_barrier(),
|
|
203
|
+
close_after_barrier(),
|
|
204
|
+
return_exceptions=True,
|
|
205
|
+
)
|
|
206
|
+
assert not any(isinstance(r, BaseException) for r in results)
|
|
207
|
+
assert session.session_id is None
|
|
208
|
+
|
|
209
|
+
await session.close()
|
|
210
|
+
|
|
211
|
+
with pytest.raises(RuntimeError, match="Session is closed.*Open a new session"):
|
|
212
|
+
await SessionMarker.all()
|
|
213
|
+
|
|
214
|
+
|
|
185
215
|
@pytest.mark.asyncio
|
|
186
216
|
async def test_nested_session_cross_context_close_inner_then_outer(tmp_path):
|
|
187
217
|
app_db = tmp_path / "app.db"
|
|
@@ -345,6 +375,144 @@ async def test_transaction_after_cross_context_close_raises(tmp_path):
|
|
|
345
375
|
pass
|
|
346
376
|
|
|
347
377
|
|
|
378
|
+
_ACTIVE_TX_CLOSE_MSG = (
|
|
379
|
+
"Cannot close session while transactions are active. "
|
|
380
|
+
"Exit all transaction() blocks before closing the session."
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@pytest.mark.asyncio
|
|
385
|
+
async def test_close_rejected_during_active_transaction_cross_context(tmp_path):
|
|
386
|
+
app_db = tmp_path / "app.db"
|
|
387
|
+
await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
|
|
388
|
+
|
|
389
|
+
session = ferro.engines.session("default")
|
|
390
|
+
await session.__aenter__()
|
|
391
|
+
|
|
392
|
+
async with ferro.transaction():
|
|
393
|
+
close_error: BaseException | None = None
|
|
394
|
+
|
|
395
|
+
async def try_close() -> None:
|
|
396
|
+
nonlocal close_error
|
|
397
|
+
try:
|
|
398
|
+
await session.close()
|
|
399
|
+
except BaseException as exc:
|
|
400
|
+
close_error = exc
|
|
401
|
+
|
|
402
|
+
await asyncio.create_task(try_close())
|
|
403
|
+
|
|
404
|
+
assert isinstance(close_error, RuntimeError)
|
|
405
|
+
assert str(close_error) == _ACTIVE_TX_CLOSE_MSG
|
|
406
|
+
assert session.session_id is not None
|
|
407
|
+
|
|
408
|
+
created = await SessionMarker.create(label="during-tx")
|
|
409
|
+
assert created.id is not None
|
|
410
|
+
|
|
411
|
+
fetched = await SessionMarker.where(lambda t: t.label == "during-tx").first()
|
|
412
|
+
assert fetched is not None
|
|
413
|
+
|
|
414
|
+
await session.close()
|
|
415
|
+
assert session.session_id is None
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@pytest.mark.asyncio
|
|
419
|
+
async def test_close_rejected_during_active_transaction_same_context(tmp_path):
|
|
420
|
+
app_db = tmp_path / "app.db"
|
|
421
|
+
await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
|
|
422
|
+
|
|
423
|
+
session = ferro.engines.session("default")
|
|
424
|
+
await session.__aenter__()
|
|
425
|
+
|
|
426
|
+
async with ferro.transaction():
|
|
427
|
+
with pytest.raises(RuntimeError, match=re.escape(_ACTIVE_TX_CLOSE_MSG)):
|
|
428
|
+
await session.close()
|
|
429
|
+
assert session.session_id is not None
|
|
430
|
+
created = await SessionMarker.create(label="same-context")
|
|
431
|
+
assert created.id is not None
|
|
432
|
+
|
|
433
|
+
fetched = await SessionMarker.where(lambda t: t.label == "same-context").first()
|
|
434
|
+
assert fetched is not None
|
|
435
|
+
|
|
436
|
+
await session.close()
|
|
437
|
+
assert session.session_id is None
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@pytest.mark.asyncio
|
|
441
|
+
async def test_close_rejected_during_active_transaction_rollback(tmp_path):
|
|
442
|
+
app_db = tmp_path / "app.db"
|
|
443
|
+
await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
|
|
444
|
+
|
|
445
|
+
session = ferro.engines.session("default")
|
|
446
|
+
await session.__aenter__()
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
async with ferro.transaction():
|
|
450
|
+
with pytest.raises(RuntimeError, match=re.escape(_ACTIVE_TX_CLOSE_MSG)):
|
|
451
|
+
await session.close()
|
|
452
|
+
assert session.session_id is not None
|
|
453
|
+
await SessionMarker.create(label="rolled-back")
|
|
454
|
+
raise RuntimeError("abort tx")
|
|
455
|
+
except RuntimeError:
|
|
456
|
+
pass
|
|
457
|
+
|
|
458
|
+
assert session.session_id is not None
|
|
459
|
+
assert not await SessionMarker.where(lambda t: t.label == "rolled-back").exists()
|
|
460
|
+
|
|
461
|
+
await session.close()
|
|
462
|
+
assert session.session_id is None
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@pytest.mark.asyncio
|
|
466
|
+
async def test_close_rejected_with_explicit_transaction_session(tmp_path):
|
|
467
|
+
app_db = tmp_path / "app.db"
|
|
468
|
+
await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
|
|
469
|
+
|
|
470
|
+
session = ferro.engines.session("default")
|
|
471
|
+
await session.__aenter__()
|
|
472
|
+
|
|
473
|
+
async with ferro.transaction(session=session):
|
|
474
|
+
with pytest.raises(RuntimeError, match=re.escape(_ACTIVE_TX_CLOSE_MSG)):
|
|
475
|
+
await session.close()
|
|
476
|
+
assert session.session_id is not None
|
|
477
|
+
created = await SessionMarker.create(label="explicit-session")
|
|
478
|
+
assert created.id is not None
|
|
479
|
+
|
|
480
|
+
fetched = await SessionMarker.where(lambda t: t.label == "explicit-session").first()
|
|
481
|
+
assert fetched is not None
|
|
482
|
+
|
|
483
|
+
await session.close()
|
|
484
|
+
assert session.session_id is None
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
@pytest.mark.asyncio
|
|
488
|
+
async def test_close_rejected_during_nested_transaction(tmp_path):
|
|
489
|
+
app_db = tmp_path / "app.db"
|
|
490
|
+
await ferro.connect(f"sqlite:{app_db}?mode=rwc", auto_migrate=True)
|
|
491
|
+
|
|
492
|
+
session = ferro.engines.session("default")
|
|
493
|
+
await session.__aenter__()
|
|
494
|
+
|
|
495
|
+
async with ferro.transaction():
|
|
496
|
+
async with ferro.transaction():
|
|
497
|
+
with pytest.raises(RuntimeError, match=re.escape(_ACTIVE_TX_CLOSE_MSG)):
|
|
498
|
+
await session.close()
|
|
499
|
+
assert session.session_id is not None
|
|
500
|
+
inner = await SessionMarker.create(label="nested-inner")
|
|
501
|
+
assert inner.id is not None
|
|
502
|
+
|
|
503
|
+
with pytest.raises(RuntimeError, match=re.escape(_ACTIVE_TX_CLOSE_MSG)):
|
|
504
|
+
await session.close()
|
|
505
|
+
assert session.session_id is not None
|
|
506
|
+
outer = await SessionMarker.create(label="nested-outer")
|
|
507
|
+
assert outer.id is not None
|
|
508
|
+
|
|
509
|
+
assert await SessionMarker.where(lambda t: t.label == "nested-inner").exists()
|
|
510
|
+
assert await SessionMarker.where(lambda t: t.label == "nested-outer").exists()
|
|
511
|
+
|
|
512
|
+
await session.close()
|
|
513
|
+
assert session.session_id is None
|
|
514
|
+
|
|
515
|
+
|
|
348
516
|
@pytest.mark.asyncio
|
|
349
517
|
async def test_aexit_preserves_body_exception_when_close_fails(tmp_path):
|
|
350
518
|
from ferro.state import _CURRENT_SESSION
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-04-29-002-feat-named-connections-plan.md
RENAMED
|
File without changes
|
|
File without changes
|
{ferro_orm-0.12.2 → ferro_orm-0.12.3}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md
RENAMED
|
File without changes
|