ferro-orm 0.13.0__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/workflows/ci.yml +5 -91
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/workflows/release.yml +19 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/AGENTS.md +35 -11
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/CHANGELOG.md +200 -0
- ferro_orm-0.15.0/CONTEXT.md +89 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/Cargo.lock +160 -26
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/Cargo.toml +9 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/PKG-INFO +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/README.md +1 -1
- ferro_orm-0.15.0/benchmarks/README.md +133 -0
- ferro_orm-0.15.0/benchmarks/__init__.py +11 -0
- ferro_orm-0.15.0/benchmarks/__main__.py +10 -0
- ferro_orm-0.15.0/benchmarks/backends.py +88 -0
- ferro_orm-0.15.0/benchmarks/baselines/postgres.json +49 -0
- ferro_orm-0.15.0/benchmarks/baselines/sqlite.json +49 -0
- ferro_orm-0.15.0/benchmarks/compare.py +98 -0
- ferro_orm-0.15.0/benchmarks/harness.py +99 -0
- ferro_orm-0.15.0/benchmarks/model.py +84 -0
- ferro_orm-0.15.0/benchmarks/run.py +270 -0
- ferro_orm-0.15.0/crates/ferro-ddl-lowering/src/lib.rs +1600 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/crates/ferro-migrate/src/emit.rs +155 -83
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/crates/ferro-migrate/src/tests.rs +291 -45
- ferro_orm-0.15.0/crates/ferro-schema-ir/src/lib.rs +603 -0
- ferro_orm-0.15.0/docs/adr/0001-resolved-epoch-registration.md +69 -0
- ferro_orm-0.15.0/docs/adr/0002-unified-column-fact-derivation.md +83 -0
- ferro_orm-0.15.0/docs/adr/0003-path-blind-db-type-validation.md +47 -0
- ferro_orm-0.15.0/docs/adr/0004-jsonb-postgres-only-canonical.md +69 -0
- ferro_orm-0.15.0/docs/adr/0005-jsonb-default-on-postgres.md +34 -0
- ferro_orm-0.15.0/docs/adr/0006-relation-traversal-inner-joins.md +42 -0
- ferro_orm-0.15.0/docs/adr/0007-materialization-plan-complete-instances.md +53 -0
- ferro_orm-0.15.0/docs/adr/0008-populated-relations-include.md +74 -0
- ferro_orm-0.15.0/docs/agents/domain.md +51 -0
- ferro_orm-0.15.0/docs/agents/issue-tracker.md +45 -0
- ferro_orm-0.15.0/docs/agents/triage-labels.md +15 -0
- ferro_orm-0.15.0/docs/architecture-review-2026-07-08.html +492 -0
- ferro_orm-0.15.0/docs/brainstorms/2026-07-02-ff-c1-codec-plan-design.md +150 -0
- ferro_orm-0.15.0/docs/brainstorms/2026-07-06-upgrade-guide-consolidation-requirements.md +222 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/multiple_databases.py +9 -7
- ferro_orm-0.15.0/docs/examples/mutations.py +112 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/pagination.py +11 -9
- ferro_orm-0.15.0/docs/examples/partial_selects.py +107 -0
- ferro_orm-0.15.0/docs/examples/partial_selects_annotated.py +43 -0
- ferro_orm-0.15.0/docs/examples/populated_relations.py +201 -0
- ferro_orm-0.15.0/docs/examples/populated_relations_annotated.py +59 -0
- ferro_orm-0.15.0/docs/examples/predicates.py +84 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/predicates_annotated.py +12 -10
- ferro_orm-0.15.0/docs/examples/quickstart.py +112 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/quickstart_annotated.py +7 -6
- ferro_orm-0.15.0/docs/examples/raw_sql.py +45 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/relationships.py +37 -36
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/relationships_annotated.py +18 -17
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/soft_deletes.py +11 -10
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/soft_deletes_annotated.py +8 -7
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/testing_conftest.py +3 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/timestamps.py +10 -9
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/examples/timestamps_annotated.py +7 -6
- ferro_orm-0.15.0/docs/examples/transactions.py +59 -0
- ferro_orm-0.15.0/docs/examples/traversal.py +302 -0
- ferro_orm-0.15.0/docs/examples/traversal_annotated.py +115 -0
- ferro_orm-0.15.0/docs/pages/api/exceptions.md +57 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/migrations.md +0 -2
- ferro_orm-0.15.0/docs/pages/api/queries.md +37 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/concepts/architecture.md +3 -5
- ferro_orm-0.15.0/docs/pages/concepts/identity-map.md +227 -0
- ferro_orm-0.15.0/docs/pages/concepts/query-typing.md +127 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/concepts/type-safety.md +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/faq.md +3 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/getting-started/quickstart.md +3 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/connections.md +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/models-and-fields.md +128 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/mutations.md +72 -16
- ferro_orm-0.15.0/docs/pages/guide/queries.md +606 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/relationships.md +16 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/howto/migrate-from-sqlalchemy.md +4 -4
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/howto/multiple-databases.md +5 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/howto/pagination.md +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/howto/soft-deletes.md +1 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/howto/testing.md +7 -5
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/howto/timestamps.md +1 -1
- ferro_orm-0.15.0/docs/pages/howto/upgrade-guide.md +490 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/roadmap.md +2 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +4 -4
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +5 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +3 -6
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +1 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-05-08-001-feat-typed-query-predicates-plan.md +1 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-05-13-001-feat-configurable-column-storage-types-plan.md +2 -1
- ferro_orm-0.15.0/docs/plans/2026-07-02-001-fable-fixes-roadmap.md +449 -0
- ferro_orm-0.15.0/docs/plans/2026-07-02-002-feat-ff-a-172-typed-exceptions-plan.md +37 -0
- ferro_orm-0.15.0/docs/plans/2026-07-02-003-ff-c-native-decode-design.md +105 -0
- ferro_orm-0.15.0/docs/plans/2026-07-02-004-ff-c-c2-catalog-cache-design.md +159 -0
- ferro_orm-0.15.0/docs/plans/2026-07-02-005-ff-d-identity-routing-design.md +207 -0
- ferro_orm-0.15.0/docs/plans/2026-07-02-006-ff-d-identity-routing-plan.md +1474 -0
- ferro_orm-0.15.0/docs/plans/2026-07-03-007-ff-e-registry-identity-design.md +267 -0
- ferro_orm-0.15.0/docs/plans/2026-07-03-008-ff-e-registry-identity-plan.md +1584 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-009-ff-f-query-builder-1.0-design.md +152 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-010-ff-f-query-builder-1.0-plan.md +1290 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-011-ff-g-hardening-design.md +268 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-012-ff-g-hardening-plan.md +985 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-013-ff-g2-operations-dedup-design.md +193 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-014-ff-g2-operations-dedup-plan.md +1203 -0
- ferro_orm-0.15.0/docs/plans/2026-07-06-015-docs-upgrade-guide-consolidation-plan.md +518 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/ir-first-migration-guide.md +11 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +10 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +13 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/cross-emitter-ddl-parity.md +11 -31
- ferro_orm-0.15.0/docs/solutions/patterns/derived-type-and-naming-decision-table.md +161 -0
- ferro_orm-0.15.0/docs/solutions/patterns/persistence-state.md +76 -0
- ferro_orm-0.15.0/justfile +23 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/pyproject.toml +2 -4
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/backend.rs +207 -16
- ferro_orm-0.15.0/src/codec.rs +535 -0
- ferro_orm-0.15.0/src/codec_plan.rs +613 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/connection.rs +48 -29
- ferro_orm-0.15.0/src/errors.rs +181 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/__init__.py +158 -27
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/_annotation_utils.py +92 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/_core.pyi +79 -82
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/_deprecations.py +0 -19
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/_shadow_fk_types.py +7 -44
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/base.py +2 -0
- ferro_orm-0.15.0/src/ferro/columns.py +494 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/composite_indexes.py +41 -39
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/composite_uniques.py +27 -22
- ferro_orm-0.15.0/src/ferro/exceptions.py +134 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/ir/__init__.py +2 -0
- ferro_orm-0.15.0/src/ferro/ir/compiler.py +445 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/metaclass.py +121 -88
- ferro_orm-0.15.0/src/ferro/migrations/alembic.py +273 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/models.py +278 -178
- ferro_orm-0.15.0/src/ferro/query/__init__.py +26 -0
- ferro_orm-0.15.0/src/ferro/query/builder.py +1510 -0
- ferro_orm-0.15.0/src/ferro/query/nodes.py +581 -0
- ferro_orm-0.15.0/src/ferro/query/rows.py +90 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/raw.py +17 -24
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/relations/__init__.py +71 -55
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/relations/descriptors.py +7 -26
- ferro_orm-0.15.0/src/ferro/state.py +355 -0
- ferro_orm-0.15.0/src/hydration.rs +439 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/introspect.rs +4 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/lib.rs +29 -15
- ferro_orm-0.15.0/src/migrate.rs +618 -0
- ferro_orm-0.15.0/src/naming_ffi.rs +92 -0
- ferro_orm-0.15.0/src/operations.rs +5535 -0
- ferro_orm-0.15.0/src/query.rs +1607 -0
- ferro_orm-0.15.0/src/schema.rs +587 -0
- ferro_orm-0.15.0/src/schema_bind.rs +23 -0
- ferro_orm-0.15.0/src/state.rs +1000 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/conftest.py +67 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/fixtures/ir_vectors/README.md +7 -2
- ferro_orm-0.15.0/tests/fixtures/ir_vectors/query_transaction_include_v4.json +48 -0
- ferro_orm-0.15.0/tests/fixtures/ir_vectors/query_transaction_left_join_v4.json +64 -0
- ferro_orm-0.15.0/tests/fixtures/ir_vectors/query_transaction_record_v4.json +50 -0
- ferro_orm-0.15.0/tests/fixtures/ir_vectors/query_transaction_traversal_v4.json +91 -0
- ferro_orm-0.13.0/tests/fixtures/ir_vectors/query_user_compound_v1.json → ferro_orm-0.15.0/tests/fixtures/ir_vectors/query_user_compound_v4.json +19 -8
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/fixtures/ir_vectors/schema_phase1_fixture_models_v1.json +118 -7
- ferro_orm-0.15.0/tests/fixtures/ir_vectors/schema_raw_str_pk_autoincrement_v1.json +46 -0
- ferro_orm-0.15.0/tests/static_fixtures/bad_includes.py +36 -0
- ferro_orm-0.15.0/tests/static_fixtures/bad_predicates.py +45 -0
- ferro_orm-0.15.0/tests/static_fixtures/bad_projections.py +45 -0
- ferro_orm-0.15.0/tests/static_fixtures/good_includes.py +44 -0
- ferro_orm-0.15.0/tests/static_fixtures/good_predicates.py +87 -0
- ferro_orm-0.15.0/tests/test_aggregation.py +74 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_alembic_autogenerate.py +13 -10
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_alembic_bridge.py +25 -51
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_alembic_db_type.py +23 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_alembic_nullability.py +6 -10
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_alembic_type_mapping.py +4 -2
- ferro_orm-0.15.0/tests/test_annotation_utils.py +24 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_auto_migrate.py +463 -273
- ferro_orm-0.15.0/tests/test_bulk_install.py +160 -0
- ferro_orm-0.15.0/tests/test_bulk_update.py +66 -0
- ferro_orm-0.15.0/tests/test_catalog_cache.py +244 -0
- ferro_orm-0.15.0/tests/test_codec_plan.py +81 -0
- ferro_orm-0.15.0/tests/test_column_specs.py +106 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_composite_index.py +66 -58
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_composite_unique.py +19 -34
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_connection.py +94 -33
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_connection_redaction.py +2 -2
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_constraints.py +6 -7
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_cross_emitter_parity.py +63 -74
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_crud.py +86 -59
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_db_type_cross_emitter_parity.py +192 -9
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_db_type_integration.py +56 -52
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_db_type_typing.py +14 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_db_type_validation.py +150 -1
- ferro_orm-0.15.0/tests/test_deletion.py +90 -0
- ferro_orm-0.15.0/tests/test_deprecations.py +52 -0
- ferro_orm-0.15.0/tests/test_documentation_features.py +874 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_enum_cold_hydration.py +15 -12
- ferro_orm-0.15.0/tests/test_exception_mapping.py +160 -0
- ferro_orm-0.15.0/tests/test_exceptions.py +126 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_field_wrapper.py +17 -11
- ferro_orm-0.15.0/tests/test_fk_target_pk.py +73 -0
- ferro_orm-0.15.0/tests/test_generation_counter_dirty_tracking.py +150 -0
- ferro_orm-0.15.0/tests/test_helpers.py +136 -0
- ferro_orm-0.15.0/tests/test_hydration.py +204 -0
- ferro_orm-0.15.0/tests/test_hydration_equivalence.py +275 -0
- ferro_orm-0.15.0/tests/test_identity_memory.py +80 -0
- ferro_orm-0.15.0/tests/test_identity_refresh.py +79 -0
- ferro_orm-0.15.0/tests/test_identity_scoped_invalidation.py +86 -0
- ferro_orm-0.15.0/tests/test_identity_weakref.py +37 -0
- ferro_orm-0.15.0/tests/test_import_budget.py +36 -0
- ferro_orm-0.15.0/tests/test_include.py +820 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_ir_vectors_contract.py +145 -39
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_metadata.py +25 -22
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_migrate_plan.py +203 -15
- ferro_orm-0.15.0/tests/test_model_identity.py +234 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_models.py +4 -1
- ferro_orm-0.15.0/tests/test_mutation_pagination_guard.py +99 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_named_connections_integration.py +58 -34
- ferro_orm-0.15.0/tests/test_naming_single_source.py +315 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_one_to_one.py +22 -20
- ferro_orm-0.15.0/tests/test_operation_seam_sync.py +269 -0
- ferro_orm-0.15.0/tests/test_partial_selects.py +474 -0
- ferro_orm-0.15.0/tests/test_provisional_import.py +231 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_query_builder.py +121 -113
- ferro_orm-0.15.0/tests/test_query_column_validation.py +153 -0
- ferro_orm-0.15.0/tests/test_query_immutability.py +101 -0
- ferro_orm-0.15.0/tests/test_query_joins.py +1177 -0
- ferro_orm-0.15.0/tests/test_query_typing.py +173 -0
- ferro_orm-0.15.0/tests/test_raw_sql.py +528 -0
- ferro_orm-0.15.0/tests/test_refresh.py +55 -0
- ferro_orm-0.15.0/tests/test_registry_entrypoints.py +294 -0
- ferro_orm-0.15.0/tests/test_relation_specs.py +175 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_relationship_engine.py +10 -2
- ferro_orm-0.15.0/tests/test_relationship_resolution_errors.py +55 -0
- ferro_orm-0.15.0/tests/test_route_single_site.py +33 -0
- ferro_orm-0.15.0/tests/test_routing_errors.py +64 -0
- ferro_orm-0.15.0/tests/test_save_pk_edges.py +53 -0
- ferro_orm-0.15.0/tests/test_save_semantics.py +358 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_schema.py +16 -15
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_schema_db_type_metadata.py +14 -14
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_schema_enum_annotations.py +2 -3
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_session.py +16 -20
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_shadow_fk_types.py +58 -54
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_sqlite_alembic_reconnect_hydration.py +21 -11
- ferro_orm-0.15.0/tests/test_static_contracts.py +223 -0
- ferro_orm-0.15.0/tests/test_string_search.py +54 -0
- ferro_orm-0.15.0/tests/test_structural_types.py +524 -0
- ferro_orm-0.15.0/tests/test_temporal_types.py +75 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_transactions.py +134 -117
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_typed_null_binds.py +130 -115
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_typed_save_bind.py +75 -67
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/uv.lock +28 -1
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/zensical.toml +2 -2
- ferro_orm-0.13.0/crates/ferro-ddl-lowering/src/lib.rs +0 -875
- ferro_orm-0.13.0/crates/ferro-schema-ir/src/lib.rs +0 -294
- ferro_orm-0.13.0/docs/examples/mutations.py +0 -49
- ferro_orm-0.13.0/docs/examples/predicates.py +0 -92
- ferro_orm-0.13.0/docs/examples/quickstart.py +0 -111
- ferro_orm-0.13.0/docs/examples/raw_sql.py +0 -43
- ferro_orm-0.13.0/docs/examples/transactions.py +0 -58
- ferro_orm-0.13.0/docs/pages/api/exceptions.md +0 -5
- ferro_orm-0.13.0/docs/pages/api/queries.md +0 -11
- ferro_orm-0.13.0/docs/pages/concepts/identity-map.md +0 -151
- ferro_orm-0.13.0/docs/pages/concepts/query-typing.md +0 -106
- ferro_orm-0.13.0/docs/pages/guide/queries.md +0 -177
- ferro_orm-0.13.0/docs/pages/howto/migrating-to-v0-12-0.md +0 -153
- ferro_orm-0.13.0/justfile +0 -11
- ferro_orm-0.13.0/src/codec.rs +0 -611
- ferro_orm-0.13.0/src/ferro/exceptions.py +0 -17
- ferro_orm-0.13.0/src/ferro/ir/compiler.py +0 -430
- ferro_orm-0.13.0/src/ferro/migrations/alembic.py +0 -569
- ferro_orm-0.13.0/src/ferro/query/__init__.py +0 -14
- ferro_orm-0.13.0/src/ferro/query/builder.py +0 -576
- ferro_orm-0.13.0/src/ferro/query/nodes.py +0 -399
- ferro_orm-0.13.0/src/ferro/schema_metadata.py +0 -167
- ferro_orm-0.13.0/src/ferro/state.py +0 -166
- ferro_orm-0.13.0/src/hydration.rs +0 -85
- ferro_orm-0.13.0/src/migrate.rs +0 -2312
- ferro_orm-0.13.0/src/operations.rs +0 -3617
- ferro_orm-0.13.0/src/query.rs +0 -917
- ferro_orm-0.13.0/src/schema.rs +0 -569
- ferro_orm-0.13.0/src/schema_bind.rs +0 -49
- ferro_orm-0.13.0/src/state.rs +0 -415
- ferro_orm-0.13.0/tests/fixtures/shadow_reports/postgres.json +0 -66
- ferro_orm-0.13.0/tests/fixtures/shadow_reports/sqlite.json +0 -66
- ferro_orm-0.13.0/tests/test_aggregation.py +0 -70
- ferro_orm-0.13.0/tests/test_bulk_update.py +0 -62
- ferro_orm-0.13.0/tests/test_deletion.py +0 -87
- ferro_orm-0.13.0/tests/test_deprecated_operator_inventory.py +0 -51
- ferro_orm-0.13.0/tests/test_deprecations.py +0 -74
- ferro_orm-0.13.0/tests/test_documentation_features.py +0 -833
- ferro_orm-0.13.0/tests/test_framework_predicates.py +0 -72
- ferro_orm-0.13.0/tests/test_helpers.py +0 -129
- ferro_orm-0.13.0/tests/test_hydration.py +0 -167
- ferro_orm-0.13.0/tests/test_query_typing.py +0 -295
- ferro_orm-0.13.0/tests/test_raw_sql.py +0 -493
- ferro_orm-0.13.0/tests/test_refresh.py +0 -53
- ferro_orm-0.13.0/tests/test_shadow_reports.py +0 -134
- ferro_orm-0.13.0/tests/test_static_contracts.py +0 -8
- ferro_orm-0.13.0/tests/test_string_search.py +0 -52
- ferro_orm-0.13.0/tests/test_structural_types.py +0 -428
- ferro_orm-0.13.0/tests/test_temporal_types.py +0 -71
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.gitignore +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/.python-version +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/LICENSE +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/crates/ferro-ddl-lowering/Cargo.toml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/crates/ferro-migrate/Cargo.toml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/crates/ferro-migrate/src/lib.rs +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/crates/ferro-schema-ir/Cargo.toml +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-05-08-typed-query-predicates-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-05-13-configurable-column-storage-types-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-05-14-autogenerate-support-for-db-type-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-05-25-annotated-strenum-cold-hydration-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-24-ir-p8-119-wire-automigrate-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-25-ir-p8-120-parity-gate-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-26-ir-p8.5-140-shared-lowering-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-26-ir-p8.5-143-db-type-drop-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-26-ir-p8.5-144-reconcile-indexes-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-28-ir-p8.5-141-single-schema-ir-producer-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-29-ir-p8.6-146-dialect-enum-unification-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-29-ir-p8.6-153-create-path-ir-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-30-ir-p8.6-155-deferred-annotations-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-06-30-ir-p8.6-158-check-renderer-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-07-01-ir-p8.6-154-datetime-tz-coarseness-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-07-01-ir-p8.6-162-typed-save-bind-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/brainstorms/2026-07-01-ir-p8.6-165-blob-introspection-false-positive-requirements.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/connection.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/fields.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/model.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/raw-sql.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/relationships.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/api/transactions.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/changelog.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/concepts/backends.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/concepts/performance.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/contributing.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/getting-started/installation.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/getting-started/next-steps.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/migrations.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/raw-sql.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/guide/transactions.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/index.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/stylesheets/extra.css +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/pages/why-ferro.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-05-25-001-fix-annotated-strenum-cold-hydration-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-19-001-ir-first-roadmap.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-24-001-feat-ir-p8-119-wire-automigrate-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-25-001-feat-ir-p8-120-parity-gate-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-26-001-feat-ir-p8.5-140-shared-lowering-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-26-002-feat-ir-p8.5-143-db-type-drop-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-26-003-feat-ir-p8.5-144-reconcile-indexes-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-28-001-feat-ir-p8.5-141-single-schema-ir-producer-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-29-001-refactor-ir-p8.6-153-create-path-unification-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-29-002-refactor-ir-p8.6-146-dialect-enum-unification-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-30-001-refactor-ir-p8.6-158-check-renderer-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-06-30-002-fix-ir-p8.6-155-deferred-annotations-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-07-01-001-feat-ir-p8.6-162-typed-save-bind-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-07-01-002-fix-ir-p8.6-154-datetime-tz-coarseness-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/2026-07-01-003-fix-ir-p8.6-165-blob-introspection-plan.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/plans/ir-first-release-checklist.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/rfc/ir-contracts-v1.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/README.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/architecture-patterns/ir-first-lowering-consolidation-audit.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/architecture-patterns/ir-first-merge-readiness-review.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/pydantic-slots-missing-after-ferro-hydration.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/sqlite-integer-decimal-hydrates-as-none.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/sqlite-null-hydrates-as-int-zero.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/issues/typed-where-null-panics-is-null.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/configurable-column-storage-types.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/ddl-on-live-engine.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/foreign-key-index.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/ir-invariants.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/sqlite-alembic-reconnect-hydration-tests.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/docs/solutions/patterns/typed-null-binds.md +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/scripts/demo_queries_pre_v012.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/_bind_payload.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/fields.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/py.typed +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/src/ferro/session.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/__init__.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/db_backends.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/fixtures/ir_vectors/codec_registry_core_v1.json +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/fixtures/ir_vectors/schema_invoice_baseline_v1.json +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_db_backends.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.13.0 → ferro_orm-0.15.0}/tests/test_schema_constraints.py +0 -0
|
@@ -15,33 +15,6 @@ permissions:
|
|
|
15
15
|
contents: read
|
|
16
16
|
|
|
17
17
|
jobs:
|
|
18
|
-
changed-shadow-paths:
|
|
19
|
-
name: Detect Shadow-Report Paths
|
|
20
|
-
runs-on: ubuntu-latest
|
|
21
|
-
if: github.event_name == 'pull_request'
|
|
22
|
-
outputs:
|
|
23
|
-
requires_shadow_reports: ${{ steps.filter.outputs.shadow }}
|
|
24
|
-
steps:
|
|
25
|
-
- name: Checkout repository
|
|
26
|
-
uses: actions/checkout@v4
|
|
27
|
-
|
|
28
|
-
- name: Detect planner/IR path changes
|
|
29
|
-
id: filter
|
|
30
|
-
uses: dorny/paths-filter@v3
|
|
31
|
-
with:
|
|
32
|
-
filters: |
|
|
33
|
-
shadow:
|
|
34
|
-
- 'src/operations.rs'
|
|
35
|
-
- 'src/query.rs'
|
|
36
|
-
- 'src/schema.rs'
|
|
37
|
-
- 'src/migrate.rs'
|
|
38
|
-
- 'src/backend.rs'
|
|
39
|
-
- 'src/connection.rs'
|
|
40
|
-
- 'src/ferro/ir/**'
|
|
41
|
-
- 'crates/ferro-schema-ir/**'
|
|
42
|
-
- 'tests/test_shadow_reports.py'
|
|
43
|
-
- 'tests/fixtures/shadow_reports/**'
|
|
44
|
-
|
|
45
18
|
lint-and-format:
|
|
46
19
|
name: Lint & Format (Pre-commit / Prek)
|
|
47
20
|
runs-on: ubuntu-latest
|
|
@@ -152,6 +125,10 @@ jobs:
|
|
|
152
125
|
run: |
|
|
153
126
|
uv run maturin develop
|
|
154
127
|
|
|
128
|
+
- name: Static type gate (ty, scoped)
|
|
129
|
+
run: |
|
|
130
|
+
uv run ty check src/ferro/query tests/test_query_typing.py tests/test_static_contracts.py
|
|
131
|
+
|
|
155
132
|
- name: Run IR vector contract harness
|
|
156
133
|
run: |
|
|
157
134
|
uv run pytest -v tests/test_ir_vectors_contract.py
|
|
@@ -270,64 +247,6 @@ jobs:
|
|
|
270
247
|
run: |
|
|
271
248
|
uv run pytest -v -m "backend_matrix or postgres_only" --db-backends=sqlite,postgres
|
|
272
249
|
|
|
273
|
-
test-shadow-reports-pr:
|
|
274
|
-
name: Shadow reports (touched paths)
|
|
275
|
-
runs-on: ubuntu-latest
|
|
276
|
-
needs: [changed-shadow-paths]
|
|
277
|
-
if: github.event_name == 'pull_request' && needs.changed-shadow-paths.outputs.requires_shadow_reports == 'true'
|
|
278
|
-
services:
|
|
279
|
-
postgres:
|
|
280
|
-
image: postgres:17
|
|
281
|
-
env:
|
|
282
|
-
POSTGRES_USER: ferro
|
|
283
|
-
POSTGRES_PASSWORD: ferro
|
|
284
|
-
POSTGRES_DB: ferro
|
|
285
|
-
ports:
|
|
286
|
-
- 5432:5432
|
|
287
|
-
options: >-
|
|
288
|
-
--health-cmd "pg_isready -U ferro -d ferro"
|
|
289
|
-
--health-interval 10s
|
|
290
|
-
--health-timeout 5s
|
|
291
|
-
--health-retries 5
|
|
292
|
-
env:
|
|
293
|
-
FERRO_SUPABASE_URL: postgresql://ferro:ferro@127.0.0.1:5432/ferro?sslmode=disable
|
|
294
|
-
FERRO_SHADOW_RUNTIME: "1"
|
|
295
|
-
FERRO_SHADOW_RUNTIME_STRICT: "1"
|
|
296
|
-
steps:
|
|
297
|
-
- name: Checkout repository
|
|
298
|
-
uses: actions/checkout@v4
|
|
299
|
-
|
|
300
|
-
- name: Set up Python
|
|
301
|
-
uses: actions/setup-python@v5
|
|
302
|
-
with:
|
|
303
|
-
python-version: '3.13'
|
|
304
|
-
|
|
305
|
-
- name: Install UV
|
|
306
|
-
uses: astral-sh/setup-uv@v5
|
|
307
|
-
with:
|
|
308
|
-
enable-cache: true
|
|
309
|
-
|
|
310
|
-
- name: Set up Rust
|
|
311
|
-
uses: dtolnay/rust-toolchain@stable
|
|
312
|
-
|
|
313
|
-
- name: Cache Rust build
|
|
314
|
-
uses: Swatinem/rust-cache@v2
|
|
315
|
-
with:
|
|
316
|
-
prefix-key: v1
|
|
317
|
-
cache-on-failure: true
|
|
318
|
-
|
|
319
|
-
- name: Install dependencies
|
|
320
|
-
run: |
|
|
321
|
-
uv sync --only-group ci-test --no-install-project --python 3.13
|
|
322
|
-
|
|
323
|
-
- name: Build Rust extension
|
|
324
|
-
run: |
|
|
325
|
-
uv run maturin develop
|
|
326
|
-
|
|
327
|
-
- name: Verify stable shadow reports
|
|
328
|
-
run: |
|
|
329
|
-
uv run pytest -v tests/test_shadow_reports.py::test_shadow_report_fixture_stable --db-backends=sqlite,postgres
|
|
330
|
-
|
|
331
250
|
check-conventional-commits:
|
|
332
251
|
name: Check Conventional Commits
|
|
333
252
|
runs-on: ubuntu-latest
|
|
@@ -377,7 +296,7 @@ jobs:
|
|
|
377
296
|
|
|
378
297
|
all-checks:
|
|
379
298
|
name: All Checks Passed
|
|
380
|
-
needs: [
|
|
299
|
+
needs: [lint-and-format, test-python-pr, test-python-main, test-python-backend-matrix, test-rust]
|
|
381
300
|
runs-on: ubuntu-latest
|
|
382
301
|
if: always()
|
|
383
302
|
steps:
|
|
@@ -386,15 +305,10 @@ jobs:
|
|
|
386
305
|
run: |
|
|
387
306
|
ok() { [[ "$1" == "success" || "$1" == "skipped" ]]; }
|
|
388
307
|
|
|
389
|
-
if [[ "${{ needs.changed-shadow-paths.result }}" == "failure" ]]; then
|
|
390
|
-
echo "Shadow path detection failed; refusing to treat shadow gate as passed."
|
|
391
|
-
exit 1
|
|
392
|
-
fi
|
|
393
308
|
if ! ok "${{ needs.lint-and-format.result }}"; then exit 1; fi
|
|
394
309
|
if ! ok "${{ needs.test-python-pr.result }}"; then exit 1; fi
|
|
395
310
|
if ! ok "${{ needs.test-python-main.result }}"; then exit 1; fi
|
|
396
311
|
if ! ok "${{ needs.test-python-backend-matrix.result }}"; then exit 1; fi
|
|
397
|
-
if ! ok "${{ needs.test-shadow-reports-pr.result }}"; then exit 1; fi
|
|
398
312
|
if ! ok "${{ needs.test-rust.result }}"; then exit 1; fi
|
|
399
313
|
|
|
400
314
|
echo "All checks passed!"
|
|
@@ -8,6 +8,11 @@ on:
|
|
|
8
8
|
required: false
|
|
9
9
|
type: boolean
|
|
10
10
|
default: false
|
|
11
|
+
highlights:
|
|
12
|
+
description: 'Markdown prepended to the top of the GitHub Release body'
|
|
13
|
+
required: false
|
|
14
|
+
type: string
|
|
15
|
+
default: ''
|
|
11
16
|
|
|
12
17
|
concurrency:
|
|
13
18
|
group: release-${{ github.workflow }}-${{ github.ref }}
|
|
@@ -185,6 +190,20 @@ jobs:
|
|
|
185
190
|
echo "release_ref=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
|
186
191
|
echo "Using release ref: $RELEASE_TAG"
|
|
187
192
|
|
|
193
|
+
- name: Prepend highlights to GitHub Release
|
|
194
|
+
if: steps.verify_release.outputs.release_created == 'true' && inputs.highlights != ''
|
|
195
|
+
env:
|
|
196
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
|
197
|
+
# Passed via env (not inline ${{ }}) so backticks, $, and quotes in the
|
|
198
|
+
# prose can't break the script or inject shell.
|
|
199
|
+
HIGHLIGHTS: ${{ inputs.highlights }}
|
|
200
|
+
shell: bash
|
|
201
|
+
run: |
|
|
202
|
+
TAG="${{ steps.release_ref.outputs.release_ref }}"
|
|
203
|
+
BODY="$(gh release view "$TAG" --json body -q .body)"
|
|
204
|
+
{ printf '%s\n' "$HIGHLIGHTS"; printf '\n---\n\n'; printf '%s\n' "$BODY"; } > /tmp/notes.md
|
|
205
|
+
gh release edit "$TAG" --notes-file /tmp/notes.md
|
|
206
|
+
|
|
188
207
|
- name: Release summary
|
|
189
208
|
if: always()
|
|
190
209
|
shell: bash
|
|
@@ -27,18 +27,23 @@ For a single model, every emitter must agree on:
|
|
|
27
27
|
|
|
28
28
|
1. **Table name** — already handled by `model_name.lower()`.
|
|
29
29
|
2. **Column names** — including shadow `*_id` columns from `ForeignKey`.
|
|
30
|
-
3. **Column types** —
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
`
|
|
35
|
-
|
|
36
|
-
`tests/test_db_type_cross_emitter_parity.py
|
|
30
|
+
3. **Column types** — decided by ONE function:
|
|
31
|
+
`ferro_ddl_lowering::resolve_column_storage` (explicit `db_type` token →
|
|
32
|
+
native-enum resolution → the `canonical_from_parts` cascade). The Alembic
|
|
33
|
+
bridge consumes it mechanically over FFI (`_core._resolve_storage_type`)
|
|
34
|
+
and `_db_type_to_sa_type` is only the SA *rendering* of the shared token
|
|
35
|
+
vocabulary — never a second decision table. Pinned exhaustively by
|
|
36
|
+
`tests/test_db_type_cross_emitter_parity.py` (every token and every
|
|
37
|
+
derived annotation × both dialects). See
|
|
38
|
+
`docs/solutions/patterns/derived-type-and-naming-decision-table.md`.
|
|
37
39
|
4. **Index names** — `idx_<table>_<col>` for single-column indexes,
|
|
38
40
|
`idx_<table>_<col1>_<col2>...` for composite indexes.
|
|
39
41
|
5. **Unique constraint names** — `uq_<table>_<col>` for single-column,
|
|
40
42
|
`uq_<table>_<col1>_<col2>...` for composite.
|
|
41
|
-
6. **Foreign key constraint names** —
|
|
43
|
+
6. **Foreign key constraint names** — `fk_<table>_<col>_<to_table>`, always
|
|
44
|
+
emitted (both emitters render `SchemaForeignKey.name`; single-sourced in
|
|
45
|
+
`ferro_ddl_lowering::fk_name`). See
|
|
46
|
+
`docs/solutions/patterns/derived-type-and-naming-decision-table.md`.
|
|
42
47
|
7. **Primary key constraint names** — when explicitly named.
|
|
43
48
|
8. **Check constraint names** — `ck_<table>_<col>` for the single-column
|
|
44
49
|
`db_check=True` constraint; generated by `_ck_constraint_name` (Python)
|
|
@@ -244,9 +249,12 @@ Rules:
|
|
|
244
249
|
checking (`User.age >= 18` types as `bool`; `where()` expects
|
|
245
250
|
`QueryNode | Predicate`). Docs say so explicitly wherever the style is
|
|
246
251
|
shown.
|
|
247
|
-
- **`order_by` is not a predicate
|
|
248
|
-
(`order_by(
|
|
249
|
-
|
|
252
|
+
- **`order_by` is not a predicate**, but its lambda selector
|
|
253
|
+
(`order_by(lambda u: u.age, "desc")`) is validated and is the documented
|
|
254
|
+
style — and it is the ONLY way to order by a related column
|
|
255
|
+
(`order_by(lambda t: t.account.label)`), which relation traversal requires.
|
|
256
|
+
Attribute style is not accepted — `order_by` takes a column-name string or
|
|
257
|
+
a lambda selector (anything else raises `TypeError`).
|
|
250
258
|
|
|
251
259
|
The canonical comparisons live in `docs/pages/guide/queries.md`
|
|
252
260
|
("Predicate Styles") and `docs/pages/concepts/query-typing.md`; everywhere
|
|
@@ -313,3 +321,19 @@ Rules:
|
|
|
313
321
|
This applies to brainstorming, design discussions, PR descriptions, issue
|
|
314
322
|
comments, and any explanation directed at the maintainer. It governs how work is
|
|
315
323
|
communicated, not what gets built.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Agent skills
|
|
328
|
+
|
|
329
|
+
### Issue tracker
|
|
330
|
+
|
|
331
|
+
GitHub Issues on `syn54x/ferro-orm`; external PRs are a triage surface. See `docs/agents/issue-tracker.md`.
|
|
332
|
+
|
|
333
|
+
### Triage labels
|
|
334
|
+
|
|
335
|
+
Canonical five-role vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`.
|
|
336
|
+
|
|
337
|
+
### Domain docs
|
|
338
|
+
|
|
339
|
+
Single-context — `CONTEXT.md` at repo root, ADRs in `docs/adr/`. See `docs/agents/domain.md`.
|
|
@@ -1,6 +1,206 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.15.0 (2026-07-11)
|
|
5
|
+
|
|
6
|
+
### Chores
|
|
7
|
+
|
|
8
|
+
- ADR-0008 populated relations + include/populated-relation glossary
|
|
9
|
+
([`a1b273d`](https://github.com/syn54x/ferro-orm/commit/a1b273dc71b2b0f77e08e69808281ab5c25ffaea))
|
|
10
|
+
|
|
11
|
+
- Amend registration adr with review outcomes (operation-seam sync, build-then-swap, deregistration)
|
|
12
|
+
([`fcb8db3`](https://github.com/syn54x/ferro-orm/commit/fcb8db385ef5c835121420b0d90c4a9bbfd8a1f8))
|
|
13
|
+
|
|
14
|
+
- New adrs and context
|
|
15
|
+
([`3ea617c`](https://github.com/syn54x/ferro-orm/commit/3ea617c2ff418aa91fdf2d72470826b0aeec7242))
|
|
16
|
+
|
|
17
|
+
- Pin failed-resolve retryability in registration adr
|
|
18
|
+
([`d5895ba`](https://github.com/syn54x/ferro-orm/commit/d5895bae311cf9a2cff230b16a975aa1a1e9a2ad))
|
|
19
|
+
|
|
20
|
+
- Pin zero-DDL, single-flight, and pure-Python clean-path invariants in registration adr
|
|
21
|
+
([`09673f3`](https://github.com/syn54x/ferro-orm/commit/09673f334128cba7af7d94db38c6f43acd241d37))
|
|
22
|
+
|
|
23
|
+
- Registration adr
|
|
24
|
+
([`05c5732`](https://github.com/syn54x/ferro-orm/commit/05c5732125370d1e2b539f83f89e200190c67bd0))
|
|
25
|
+
|
|
26
|
+
- Relation traversal ADR and context
|
|
27
|
+
([`9151856`](https://github.com/syn54x/ferro-orm/commit/9151856364ea6ccc8ca125102302b5115e6a9ae0))
|
|
28
|
+
|
|
29
|
+
- Triage and update old plan statuses
|
|
30
|
+
([`2f3e3c0`](https://github.com/syn54x/ferro-orm/commit/2f3e3c0584eec37815e2520852077b9c08885546))
|
|
31
|
+
|
|
32
|
+
### Continuous Integration
|
|
33
|
+
|
|
34
|
+
- **release**: Custom highlights atop the GitHub Release notes
|
|
35
|
+
([#275](https://github.com/syn54x/ferro-orm/pull/275),
|
|
36
|
+
[`f705938`](https://github.com/syn54x/ferro-orm/commit/f70593839af93ec902a0537f0f1ae73a34c425c2))
|
|
37
|
+
|
|
38
|
+
### Documentation
|
|
39
|
+
|
|
40
|
+
- ADR-0007 materialization plan + complete-instance glossary
|
|
41
|
+
([`4acc879`](https://github.com/syn54x/ferro-orm/commit/4acc879b23fcd5bc540b5ed7ee7129d95124ec77))
|
|
42
|
+
|
|
43
|
+
### Features
|
|
44
|
+
|
|
45
|
+
- Atomic bulk registration install with fingerprint gate (#244)
|
|
46
|
+
([#251](https://github.com/syn54x/ferro-orm/pull/251),
|
|
47
|
+
[`20c6061`](https://github.com/syn54x/ferro-orm/commit/20c6061c376faab78a7e3dc2496e5dd3cc123115))
|
|
48
|
+
|
|
49
|
+
- Compile ModelCodecPlan from SchemaIR at registration
|
|
50
|
+
([#239](https://github.com/syn54x/ferro-orm/pull/239),
|
|
51
|
+
[`7e15fdc`](https://github.com/syn54x/ferro-orm/commit/7e15fdcb9f32938857ad21838feca978591fd044))
|
|
52
|
+
|
|
53
|
+
- Generation-counter dirty tracking with assemble-not-recompile (#245)
|
|
54
|
+
([#252](https://github.com/syn54x/ferro-orm/pull/252),
|
|
55
|
+
[`2790e56`](https://github.com/syn54x/ferro-orm/commit/2790e566437d6e67f1f5c3abe555162ebb62598d))
|
|
56
|
+
|
|
57
|
+
- Joined-row hydration — QueryIR v4 + populated relations via include()
|
|
58
|
+
([#289](https://github.com/syn54x/ferro-orm/pull/289),
|
|
59
|
+
[`4335417`](https://github.com/syn54x/ferro-orm/commit/4335417cbfd33da95b6ea52971d6f5e805af9ad7))
|
|
60
|
+
|
|
61
|
+
- JSONB column support (#260) ([#266](https://github.com/syn54x/ferro-orm/pull/266),
|
|
62
|
+
[`19f8cee`](https://github.com/syn54x/ferro-orm/commit/19f8cee3b7be3a570265f1269c735ac9093ab16a))
|
|
63
|
+
|
|
64
|
+
- Partial materialization — QueryIR v3 + partial selects (Rows/Row)
|
|
65
|
+
([#283](https://github.com/syn54x/ferro-orm/pull/283),
|
|
66
|
+
[`e44129b`](https://github.com/syn54x/ferro-orm/commit/e44129bf966fa349b5bf637a5976c99b88c644b2))
|
|
67
|
+
|
|
68
|
+
- Query-time joins — relation traversal for filter and sort (stage 1)
|
|
69
|
+
([#276](https://github.com/syn54x/ferro-orm/pull/276),
|
|
70
|
+
[`55dc386`](https://github.com/syn54x/ferro-orm/commit/55dc386f087fd6506d1713c9333bc11103bc96ac))
|
|
71
|
+
|
|
72
|
+
- Sync registration at ORM operation seam (#247)
|
|
73
|
+
([#254](https://github.com/syn54x/ferro-orm/pull/254),
|
|
74
|
+
[`8409322`](https://github.com/syn54x/ferro-orm/commit/84093226a86b2cdc17afdce00d36fc4782c614c4))
|
|
75
|
+
|
|
76
|
+
### Refactoring
|
|
77
|
+
|
|
78
|
+
- Canonicalize registry keys — derive register_model key from model identity
|
|
79
|
+
([#250](https://github.com/syn54x/ferro-orm/pull/250),
|
|
80
|
+
[`d835c96`](https://github.com/syn54x/ferro-orm/commit/d835c96f08094ca0a8f7df0947ba9440f956edc9))
|
|
81
|
+
|
|
82
|
+
- Centralize register/deregister registry entrypoints (#243)
|
|
83
|
+
([#248](https://github.com/syn54x/ferro-orm/pull/248),
|
|
84
|
+
[`2f2be69`](https://github.com/syn54x/ferro-orm/commit/2f2be69ac9dc859621afa5938f08b7f167af1870))
|
|
85
|
+
|
|
86
|
+
- Compile ColumnSpec column facts once (#255) ([#256](https://github.com/syn54x/ferro-orm/pull/256),
|
|
87
|
+
[`75fe1de`](https://github.com/syn54x/ferro-orm/commit/75fe1de5f3aee8f870e1e189f639b7466445c6dd))
|
|
88
|
+
|
|
89
|
+
- Drop RegisteredModel.schema — IR-first registry cleanup
|
|
90
|
+
([#241](https://github.com/syn54x/ferro-orm/pull/241),
|
|
91
|
+
[`fc3b9f9`](https://github.com/syn54x/ferro-orm/commit/fc3b9f9c50bbd80f3915663e6278712df35fee96))
|
|
92
|
+
|
|
93
|
+
- Remove legacy migration shims and shadow runtime
|
|
94
|
+
([#237](https://github.com/syn54x/ferro-orm/pull/237),
|
|
95
|
+
[`c2117b3`](https://github.com/syn54x/ferro-orm/commit/c2117b3351189912436c90552e127018db438c34))
|
|
96
|
+
|
|
97
|
+
### Testing
|
|
98
|
+
|
|
99
|
+
- Pin provisional import — Python-only registration until connect (#246)
|
|
100
|
+
([#253](https://github.com/syn54x/ferro-orm/pull/253),
|
|
101
|
+
[`ca111b5`](https://github.com/syn54x/ferro-orm/commit/ca111b5672c647cbb021fd63bf59038f0028f303))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## v0.14.0 (2026-07-07)
|
|
105
|
+
|
|
106
|
+
### Bug Fixes
|
|
107
|
+
|
|
108
|
+
- **ff-g**: Make Postgres db_check ADD CONSTRAINT idempotent (G6, #176)
|
|
109
|
+
([#181](https://github.com/syn54x/ferro-orm/pull/181),
|
|
110
|
+
[`2c0e4cf`](https://github.com/syn54x/ferro-orm/commit/2c0e4cf6c922a73454198f6aa0aa75b30eb1f0c8))
|
|
111
|
+
|
|
112
|
+
### Chores
|
|
113
|
+
|
|
114
|
+
- **benchmarks**: Pinned async benchmark suite over the rich-type hot path
|
|
115
|
+
([#196](https://github.com/syn54x/ferro-orm/pull/196),
|
|
116
|
+
[`d9a656b`](https://github.com/syn54x/ferro-orm/commit/d9a656b320bd7fb6b613f4408a796161e9badc41))
|
|
117
|
+
|
|
118
|
+
### Documentation
|
|
119
|
+
|
|
120
|
+
- Consolidate migration guides into one evergreen upgrade guide
|
|
121
|
+
([#234](https://github.com/syn54x/ferro-orm/pull/234),
|
|
122
|
+
[`15c83db`](https://github.com/syn54x/ferro-orm/commit/15c83dbc6b1c057a78dfc425d70441e50c359f5d))
|
|
123
|
+
|
|
124
|
+
- Fixes roadmap
|
|
125
|
+
([`5718d02`](https://github.com/syn54x/ferro-orm/commit/5718d02630b6c84d3ced6f8a3080adf4e7fe2383))
|
|
126
|
+
|
|
127
|
+
- **fable-fixes**: Fold #176 into Epic FF-G as sub-task G6
|
|
128
|
+
([`b966e65`](https://github.com/syn54x/ferro-orm/commit/b966e65739d7c33ed666a7fd14086a27688516dd))
|
|
129
|
+
|
|
130
|
+
- **ff-a**: A5 — docs & migration guide for the mutation-surface changes
|
|
131
|
+
([#180](https://github.com/syn54x/ferro-orm/pull/180),
|
|
132
|
+
[`ffc5476`](https://github.com/syn54x/ferro-orm/commit/ffc5476f9ba72f03a35048f283b11390f813856b))
|
|
133
|
+
|
|
134
|
+
- **ff-b**: Tick FF-B sub-task and exit-gate boxes in the fable-fixes roadmap
|
|
135
|
+
([`29f9172`](https://github.com/syn54x/ferro-orm/commit/29f91724e2b6886f8299bbb42940c804c8e88141))
|
|
136
|
+
|
|
137
|
+
### Features
|
|
138
|
+
|
|
139
|
+
- **ff-a**: Create() is a real INSERT; save() distinguishes INSERT from UPDATE (A3+A4)
|
|
140
|
+
([#179](https://github.com/syn54x/ferro-orm/pull/179),
|
|
141
|
+
[`de3ec30`](https://github.com/syn54x/ferro-orm/commit/de3ec30196202ee14c42afc5b02f163f82a68451))
|
|
142
|
+
|
|
143
|
+
- **ff-a**: Reject limit/offset on mutating queries
|
|
144
|
+
([#178](https://github.com/syn54x/ferro-orm/pull/178),
|
|
145
|
+
[`67faf42`](https://github.com/syn54x/ferro-orm/commit/67faf421d49984c5be16d2163982f13ab86cc5e4))
|
|
146
|
+
|
|
147
|
+
- **ff-a**: Typed DBAPI-shaped exception hierarchy mapped from sqlx errors
|
|
148
|
+
([#177](https://github.com/syn54x/ferro-orm/pull/177),
|
|
149
|
+
[`9c306c5`](https://github.com/syn54x/ferro-orm/commit/9c306c549e716dd4a5158d655265aa7890e63c0b))
|
|
150
|
+
|
|
151
|
+
- **ff-b**: B1 canonical derived-type & naming decision table + refusal-rail scaffolding
|
|
152
|
+
([`8c879f7`](https://github.com/syn54x/ferro-orm/commit/8c879f77833a7e5a983d53012efc8332cca5b852))
|
|
153
|
+
|
|
154
|
+
- **ff-b**: B2+B6 one derived-type decision table; native PG enums + timestamptz/time parity; delete
|
|
155
|
+
bridge mirrors
|
|
156
|
+
([`bfdc1fd`](https://github.com/syn54x/ferro-orm/commit/bfdc1fda1eceebe70ae711e38b785af88b46e11e))
|
|
157
|
+
|
|
158
|
+
- **ff-b**: B3+B4 single-source artifact naming; both emitters emit named fk_/uq_ artifacts
|
|
159
|
+
([`6b771f3`](https://github.com/syn54x/ferro-orm/commit/6b771f38933c72e8f848013a5ae93708fabf5c7a))
|
|
160
|
+
|
|
161
|
+
- **ff-c**: C1 — per-model ColumnCodec plan; delete codec.rs schema sniffing (F5)
|
|
162
|
+
([#197](https://github.com/syn54x/ferro-orm/pull/197),
|
|
163
|
+
[`0e8e572`](https://github.com/syn54x/ferro-orm/commit/0e8e57252f4cca2d2bc9fffd06d9155338056cf3))
|
|
164
|
+
|
|
165
|
+
- **ff-c**: C2 — schema-epoch catalog cache; zero catalog queries on steady-state CRUD
|
|
166
|
+
([#200](https://github.com/syn54x/ferro-orm/pull/200),
|
|
167
|
+
[`8c6d6ed`](https://github.com/syn54x/ferro-orm/commit/8c6d6edf578cf55ed7ada0d49359a7e09528da1e))
|
|
168
|
+
|
|
169
|
+
- **ff-c**: C3+C4 — native typed Postgres decode; plan-driven enum hydration replaces _fix_types
|
|
170
|
+
([#198](https://github.com/syn54x/ferro-orm/pull/198),
|
|
171
|
+
[`05a008c`](https://github.com/syn54x/ferro-orm/commit/05a008cb73f6c4dc8b2478a441c99282d07bd41d))
|
|
172
|
+
|
|
173
|
+
- **ff-d**: Session-scoped weak identity map with refresh-on-load; single-handle routing
|
|
174
|
+
([#201](https://github.com/syn54x/ferro-orm/pull/201),
|
|
175
|
+
[`a328cfc`](https://github.com/syn54x/ferro-orm/commit/a328cfcd107e2cac998ddc8af50845857ec669a5))
|
|
176
|
+
|
|
177
|
+
- **ff-e**: Registry & model identity — qualified keys, configurable tables, O(N) import
|
|
178
|
+
([#209](https://github.com/syn54x/ferro-orm/pull/209),
|
|
179
|
+
[`6391805`](https://github.com/syn54x/ferro-orm/commit/6391805c1b4ee46c0590513ac6684237c18f7430))
|
|
180
|
+
|
|
181
|
+
- **ff-f**: Query builder 1.0 shape — immutable chaining, build-time column validation, lambda-only
|
|
182
|
+
predicates, QueryIR-only Rust ([#223](https://github.com/syn54x/ferro-orm/pull/223),
|
|
183
|
+
[`eb7fece`](https://github.com/syn54x/ferro-orm/commit/eb7fece8fbc2231220ecf9111616f803f2f987eb))
|
|
184
|
+
|
|
185
|
+
- **ff-g-a**: Hardening — hydration ABI guard, transactional PG migrate, correctness edges,
|
|
186
|
+
decode-path caching ([#232](https://github.com/syn54x/ferro-orm/pull/232),
|
|
187
|
+
[`22617e1`](https://github.com/syn54x/ferro-orm/commit/22617e1cfe5e2bc9d0a3d7a1fed5acf4d8589cef))
|
|
188
|
+
|
|
189
|
+
### Refactoring
|
|
190
|
+
|
|
191
|
+
- **ff-g-b**: Operations.rs dedup — ModelMeta + Executor (G2)
|
|
192
|
+
([#233](https://github.com/syn54x/ferro-orm/pull/233),
|
|
193
|
+
[`09c2c62`](https://github.com/syn54x/ferro-orm/commit/09c2c6214af9e2f9510d39d037935c53bd2e796d))
|
|
194
|
+
|
|
195
|
+
### Testing
|
|
196
|
+
|
|
197
|
+
- **ff-b**: B5 I-1 sentinel on the full backend matrix with a full-type fixture, zero filters
|
|
198
|
+
([`1141eb4`](https://github.com/syn54x/ferro-orm/commit/1141eb437b4843303e01556a5b24ddee9d0e0279))
|
|
199
|
+
|
|
200
|
+
- **ff-b**: Force psycopg v3 driver in the Postgres sentinel regardless of URL scheme
|
|
201
|
+
([`64f9845`](https://github.com/syn54x/ferro-orm/commit/64f98454bdb5e090a41bd214de06b7670974f4eb))
|
|
202
|
+
|
|
203
|
+
|
|
4
204
|
## v0.13.0 (2026-07-02)
|
|
5
205
|
|
|
6
206
|
### Bug Fixes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Ferro ORM
|
|
2
|
+
|
|
3
|
+
A Python ORM with a Rust core. Models are Pydantic subclasses; schema compiles to SchemaIR and fans out to runtime DDL and the Alembic bridge.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Materialized View**:
|
|
8
|
+
A PostgreSQL database object that stores the result of a query and is refreshed on demand. In Ferro it is a read-only `MaterializedView` subclass — queryable through the normal ORM, not writable.
|
|
9
|
+
_Avoid_: Snapshot table, cache table, denormalized table
|
|
10
|
+
|
|
11
|
+
**Refresh**:
|
|
12
|
+
The explicit operation that repopulates a materialized view from its defining SELECT. Ferro never refreshes at connect time; the user calls `refresh()` when they want updated data.
|
|
13
|
+
_Avoid_: Auto-refresh, sync, rebuild
|
|
14
|
+
|
|
15
|
+
**Postgres-only schema object**:
|
|
16
|
+
A model artifact that Ferro emits only on PostgreSQL. On SQLite the class still registers for imports and typing, but DDL is skipped and querying raises a clear error.
|
|
17
|
+
_Avoid_: Dialect-specific model, PG-only table
|
|
18
|
+
|
|
19
|
+
**Redefine**:
|
|
20
|
+
Replacing an existing materialized view by dropping and recreating it when its defining SELECT changes. Authorized by `migrate_materialized_redefine=True`; otherwise connect fails loudly on drift.
|
|
21
|
+
_Avoid_: Alter, migrate, update
|
|
22
|
+
|
|
23
|
+
**Materialized view column**:
|
|
24
|
+
A flat, typed field on a `MaterializedView` — same declarations as `Model` fields, but no `ForeignKey`, `BackRef`, or `ManyToMany`. Reference related entities by scalar columns (`order_id: int`), not relations.
|
|
25
|
+
_Avoid_: Relation column, FK field
|
|
26
|
+
|
|
27
|
+
**Materialized query**:
|
|
28
|
+
The `ClassVar` SQL string (`__materialized_query__`) that defines what rows a materialized view stores. Declared alongside typed fields; Ferro validates that SELECT output matches the field contract.
|
|
29
|
+
_Avoid_: Select SQL, view definition, query body
|
|
30
|
+
|
|
31
|
+
**Read-only view**:
|
|
32
|
+
A `MaterializedView` that can be queried but never mutated. `save()`, `delete()`, and `create()` raise a clear error.
|
|
33
|
+
_Avoid_: Immutable model, snapshot model
|
|
34
|
+
|
|
35
|
+
**Storage token**:
|
|
36
|
+
A word in the canonical `db_type` vocabulary (`text`, `bigint`, `timestamptz`, `jsonb`, …) naming how a column is stored. One shared vocabulary feeds every emitter; a token never means different things to different emitters.
|
|
37
|
+
_Avoid_: SQL type string, dialect type, column type name
|
|
38
|
+
|
|
39
|
+
**Storage lowering**:
|
|
40
|
+
The dialect-side degrade of a storage token to the nearest type a backend supports (e.g. `jsonb` stores as plain JSON on SQLite). Lowering is silent and documented; it never changes value semantics — only the on-disk representation.
|
|
41
|
+
_Avoid_: Fallback type, emulation, downgrade
|
|
42
|
+
|
|
43
|
+
**Json-family field**:
|
|
44
|
+
A field whose values Ferro stores as JSON documents: `dict`, `list` (any element type, including nested models), or a nested Pydantic model. Only json-family fields may opt into JSON storage tokens such as `jsonb`.
|
|
45
|
+
_Avoid_: Object field, blob field, document column
|
|
46
|
+
|
|
47
|
+
**Column spec**:
|
|
48
|
+
The single authoritative record of one column's facts — identity, type, and constraints — derived exactly once from the field declaration. Provisional at class-body time; authoritative once relationship resolution completes.
|
|
49
|
+
_Avoid_: Column metadata, enriched schema property, field dict
|
|
50
|
+
|
|
51
|
+
**Relation traversal**:
|
|
52
|
+
Attribute access on a declared forward-FK field inside a query lambda (`lambda t: t.account.ledger_id`), reaching a related model's columns from the root. Traversal narrows the result to rows where the relation exists; keeping rows without the relation requires an explicit left join.
|
|
53
|
+
_Avoid_: Join inference, nested filter, path lookup, string path
|
|
54
|
+
|
|
55
|
+
**Relation path**:
|
|
56
|
+
The ordered sequence of forward-FK hops a traversal walks (`account`, or `account → owner`). A path is the identity of a join: the same path referenced anywhere in a query is one join, and distinct paths to the same model are distinct joins. Left-join requests apply to a whole path.
|
|
57
|
+
_Avoid_: Join alias, lookup chain, dotted path string
|
|
58
|
+
|
|
59
|
+
**Shape-preserving query**:
|
|
60
|
+
The invariant that filtering and ordering never change what a query returns — a query over Transaction yields Transaction instances regardless of which relations its predicates traverse. Only an explicit projection operation may change the result shape.
|
|
61
|
+
_Avoid_: Implicit projection, row narrowing
|
|
62
|
+
|
|
63
|
+
**Complete-instance invariant**:
|
|
64
|
+
A model instance always carries a complete row — there is no such thing as a partial or deferred-field model instance, anywhere. Anything narrower than a full row (a column subset, an aggregate) comes back as a projected record, never as the model type.
|
|
65
|
+
_Avoid_: Partial instance, deferred field, lightweight model, .only()
|
|
66
|
+
|
|
67
|
+
**Projected record**:
|
|
68
|
+
The result of an explicit projection — a typed record of named values that is not a model instance and cannot be saved, refreshed, or identity-mapped. Column subsets and aggregation results are projected records; complete model rows are not. Realized as `Row`, delivered in the list-like `Rows` container.
|
|
69
|
+
_Avoid_: Partial model, row dict, value tuple
|
|
70
|
+
|
|
71
|
+
**Include**:
|
|
72
|
+
The explicit request (`.include(lambda t: t.account.owner)`) that a query populate a forward-FK relation path — the data axis of a query, distinct from joins (membership) and projection (shape). Including a path populates every hop along it and never changes which rows come back.
|
|
73
|
+
_Avoid_: Eager load, select_related, prefetch, join-fetch
|
|
74
|
+
|
|
75
|
+
**Populated relation**:
|
|
76
|
+
A forward-FK field carrying its complete related instance, attached by an explicit include on the query. Attribute access returns the instance directly — no await, no query — matching the field's declared type. An unpopulated relation keeps the awaitable contract; population changes cost and attached data, never the result type (there is no separate "loaded" model type).
|
|
77
|
+
_Avoid_: Eager-loaded field, select_related, prefetched attribute, joined attribute
|
|
78
|
+
|
|
79
|
+
**Materialization plan**:
|
|
80
|
+
A query's declaration of what its result columns become: complete root instances (every query today), a projected record of named fields, or — in the future — a populated instance graph. Every query carries exactly one plan; the plan travels with the query rather than being inferred from its column list.
|
|
81
|
+
_Avoid_: Select list, projection spec, hydration mode flag
|
|
82
|
+
|
|
83
|
+
**Provisional registration**:
|
|
84
|
+
The per-model state installed when a class body finishes executing — enough for runtime codec and PK metadata, but relationships may still be pending and the modelset is not yet authoritative for DDL.
|
|
85
|
+
_Avoid_: Import-time registration, partial registry
|
|
86
|
+
|
|
87
|
+
**Resolved registration**:
|
|
88
|
+
The registry epoch after relationship resolution completes — join tables exist, shadow FK columns are wired, and the SchemaIR modelset is authoritative for DDL and auto-migrate.
|
|
89
|
+
_Avoid_: Final registration, committed registry
|