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