ferro-orm 0.6.0__tar.gz → 0.7.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.6.0 → ferro_orm-0.7.0}/CHANGELOG.md +17 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/Cargo.lock +18 -18
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/Cargo.toml +1 -1
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/PKG-INFO +1 -1
- ferro_orm-0.7.0/docs/plans/2026-05-07-001-refactor-generic-model-connection-plan.md +192 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/pyproject.toml +1 -1
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/backend.rs +17 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/connection.rs +4 -2
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/__init__.py +6 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/_core.pyi +2 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/models.py +25 -20
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/operations.rs +67 -38
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_alembic_type_mapping.py +21 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_connection.py +1 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_crud.py +21 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_named_connections_integration.py +38 -1
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_structural_types.py +42 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/uv.lock +1 -1
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/agent-native.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/api-contract.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/correctness.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/data-migrations.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/kieran-python.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/maintainability.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/project-standards.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.context/compound-engineering/ce-code-review/20260428-081705-bdeab5a2/testing.json +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/workflows/ci.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.github/workflows/release.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.gitignore +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/.python-version +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/AGENTS.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/LICENSE +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/README.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/TEST_RESULTS.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/fields.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/model.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/query.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/raw-sql.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/relationships.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/transactions.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/api/utilities.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/brainstorms/2026-04-29-named-connections-role-routing-requirements.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/changelog.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/coming-soon.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/concepts/architecture.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/concepts/identity-map.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/concepts/performance.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/concepts/type-safety.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/contributing.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/faq.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/getting-started/installation.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/getting-started/next-steps.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/getting-started/tutorial.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/backend.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/database.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/migrations.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/models-and-fields.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/mutations.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/queries.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/relationships.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/guide/transactions.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/howto/multiple-databases.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/howto/pagination.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/howto/soft-deletes.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/howto/testing.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/howto/timestamps.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/index.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/migration-sqlalchemy.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/plans/2026-04-29-001-typed-null-binds-plan.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/plans/2026-04-29-002-feat-named-connections-plan.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/README.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/issues/python-3.14-deferred-annotation-typeerror-swallow.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/issues/sa-pk-column-nullable-divergence.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/issues/sa-vs-rust-unique-constraint-shape.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/patterns/cross-emitter-ddl-parity.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/patterns/foreign-key-index.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/patterns/index-unique-redundancy.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/patterns/shadow-fk-columns.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/solutions/patterns/typed-null-binds.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/stylesheets/extra.css +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/docs/why-ferro.md +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/justfile +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/mkdocs.yml +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/_shadow_fk_types.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/base.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/composite_indexes.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/fields.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/metaclass.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/migrations/alembic.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/py.typed +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/query/__init__.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/query/builder.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/query/nodes.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/raw.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/relations/__init__.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/relations/descriptors.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/schema_metadata.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/ferro/state.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/lib.rs +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/query.rs +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/schema.rs +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/src/state.rs +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/__init__.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/conftest.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/db_backends.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_aggregation.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_alembic_bridge.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_alembic_nullability.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_auto_migrate.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_bulk_update.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_composite_index.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_composite_unique.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_connection_redaction.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_constraints.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_cross_emitter_parity.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_db_backends.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_deletion.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_documentation_features.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_field_wrapper.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_helpers.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_hydration.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_metadata.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_models.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_one_to_one.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_query_builder.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_raw_sql.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_refresh.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_relationship_engine.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_schema.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_schema_constraints.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_schema_enum_annotations.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_shadow_fk_types.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_static_contracts.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_string_search.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_temporal_types.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_transactions.py +0 -0
- {ferro_orm-0.6.0 → ferro_orm-0.7.0}/tests/test_typed_null_binds.py +0 -0
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.7.0 (2026-05-08)
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
- Per-connection identity_map on connect ([#47](https://github.com/syn54x/ferro-orm/pull/47),
|
|
9
|
+
[`0a1d629`](https://github.com/syn54x/ferro-orm/commit/0a1d62926538cde14fdd4f4deece21a59a1ede69))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## v0.6.1 (2026-05-07)
|
|
13
|
+
|
|
14
|
+
### Refactoring
|
|
15
|
+
|
|
16
|
+
- Make ModelConnection generic to preserve model typing through .using()
|
|
17
|
+
([#46](https://github.com/syn54x/ferro-orm/pull/46),
|
|
18
|
+
[`50d6b68`](https://github.com/syn54x/ferro-orm/commit/50d6b683059ce1d2b00942efd7267836db00eefd))
|
|
19
|
+
|
|
20
|
+
|
|
4
21
|
## v0.6.0 (2026-04-30)
|
|
5
22
|
|
|
6
23
|
### Features
|
|
@@ -79,9 +79,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|
|
79
79
|
|
|
80
80
|
[[package]]
|
|
81
81
|
name = "cc"
|
|
82
|
-
version = "1.2.
|
|
82
|
+
version = "1.2.62"
|
|
83
83
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
84
|
-
checksum = "
|
|
84
|
+
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
|
85
85
|
dependencies = [
|
|
86
86
|
"find-msvc-tools",
|
|
87
87
|
"shlex",
|
|
@@ -294,7 +294,7 @@ dependencies = [
|
|
|
294
294
|
|
|
295
295
|
[[package]]
|
|
296
296
|
name = "ferro"
|
|
297
|
-
version = "0.
|
|
297
|
+
version = "0.7.0"
|
|
298
298
|
dependencies = [
|
|
299
299
|
"dashmap",
|
|
300
300
|
"once_cell",
|
|
@@ -711,9 +711,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
711
711
|
|
|
712
712
|
[[package]]
|
|
713
713
|
name = "js-sys"
|
|
714
|
-
version = "0.3.
|
|
714
|
+
version = "0.3.98"
|
|
715
715
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
716
|
-
checksum = "
|
|
716
|
+
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
|
717
717
|
dependencies = [
|
|
718
718
|
"cfg-if",
|
|
719
719
|
"futures-util",
|
|
@@ -757,7 +757,7 @@ dependencies = [
|
|
|
757
757
|
"bitflags",
|
|
758
758
|
"libc",
|
|
759
759
|
"plain",
|
|
760
|
-
"redox_syscall 0.7.
|
|
760
|
+
"redox_syscall 0.7.5",
|
|
761
761
|
]
|
|
762
762
|
|
|
763
763
|
[[package]]
|
|
@@ -1136,9 +1136,9 @@ dependencies = [
|
|
|
1136
1136
|
|
|
1137
1137
|
[[package]]
|
|
1138
1138
|
name = "redox_syscall"
|
|
1139
|
-
version = "0.7.
|
|
1139
|
+
version = "0.7.5"
|
|
1140
1140
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1141
|
-
checksum = "
|
|
1141
|
+
checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b"
|
|
1142
1142
|
dependencies = [
|
|
1143
1143
|
"bitflags",
|
|
1144
1144
|
]
|
|
@@ -1699,9 +1699,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|
|
1699
1699
|
|
|
1700
1700
|
[[package]]
|
|
1701
1701
|
name = "tokio"
|
|
1702
|
-
version = "1.52.
|
|
1702
|
+
version = "1.52.3"
|
|
1703
1703
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1704
|
-
checksum = "
|
|
1704
|
+
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
|
1705
1705
|
dependencies = [
|
|
1706
1706
|
"bytes",
|
|
1707
1707
|
"libc",
|
|
@@ -1892,9 +1892,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
|
|
1892
1892
|
|
|
1893
1893
|
[[package]]
|
|
1894
1894
|
name = "wasm-bindgen"
|
|
1895
|
-
version = "0.2.
|
|
1895
|
+
version = "0.2.121"
|
|
1896
1896
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1897
|
-
checksum = "
|
|
1897
|
+
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
|
1898
1898
|
dependencies = [
|
|
1899
1899
|
"cfg-if",
|
|
1900
1900
|
"once_cell",
|
|
@@ -1905,9 +1905,9 @@ dependencies = [
|
|
|
1905
1905
|
|
|
1906
1906
|
[[package]]
|
|
1907
1907
|
name = "wasm-bindgen-macro"
|
|
1908
|
-
version = "0.2.
|
|
1908
|
+
version = "0.2.121"
|
|
1909
1909
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1910
|
-
checksum = "
|
|
1910
|
+
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
|
1911
1911
|
dependencies = [
|
|
1912
1912
|
"quote",
|
|
1913
1913
|
"wasm-bindgen-macro-support",
|
|
@@ -1915,9 +1915,9 @@ dependencies = [
|
|
|
1915
1915
|
|
|
1916
1916
|
[[package]]
|
|
1917
1917
|
name = "wasm-bindgen-macro-support"
|
|
1918
|
-
version = "0.2.
|
|
1918
|
+
version = "0.2.121"
|
|
1919
1919
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1920
|
-
checksum = "
|
|
1920
|
+
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
|
1921
1921
|
dependencies = [
|
|
1922
1922
|
"bumpalo",
|
|
1923
1923
|
"proc-macro2",
|
|
@@ -1928,9 +1928,9 @@ dependencies = [
|
|
|
1928
1928
|
|
|
1929
1929
|
[[package]]
|
|
1930
1930
|
name = "wasm-bindgen-shared"
|
|
1931
|
-
version = "0.2.
|
|
1931
|
+
version = "0.2.121"
|
|
1932
1932
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1933
|
-
checksum = "
|
|
1933
|
+
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
|
1934
1934
|
dependencies = [
|
|
1935
1935
|
"unicode-ident",
|
|
1936
1936
|
]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "refactor: Make ModelConnection generic to preserve model typing through .using()"
|
|
3
|
+
type: refactor
|
|
4
|
+
status: active
|
|
5
|
+
date: 2026-05-07
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# refactor: Make ModelConnection generic to preserve model typing through .using()
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`ModelConnection` (the object returned by `Model.using(name)`) is not generic, so every method that should return the bound model class falls back to the unbound `Model` base — `Transcript.using(SERVICE).get("...")` resolves to `Model | None` instead of `Transcript | None`. This plan parameterizes `ModelConnection` over `M: Model` using PEP 695 syntax, propagates `M` through every method return type, and renames the conflicting `self.using` instance attribute so the class isn't shadowing its own classmethod entrypoint. No behavior changes — pure typing + naming hygiene.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- R1. `Transcript.using(SERVICE).get(pk)` resolves to `Transcript | None` under Pyright/Pylance.
|
|
19
|
+
- R2. Every other `ModelConnection` method preserves the model type: `create -> M`, `all -> list[M]`, `select -> Query[M]`, `where -> Query[M]`, `bulk_create -> int`, `get_or_create -> tuple[M, bool]`, `update_or_create -> tuple[M, bool]`.
|
|
20
|
+
- R3. The instance attribute previously named `self.using` no longer shadows the `Model.using` classmethod name.
|
|
21
|
+
- R4. Existing runtime behavior is preserved — every test under `tests/test_connection.py` and `tests/test_named_connections_integration.py` continues to pass without modification.
|
|
22
|
+
- R5. Public surface is unchanged: `Model.using("name")` still returns a `ModelConnection`-shaped object with all the same methods and call signatures.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Scope Boundaries
|
|
27
|
+
|
|
28
|
+
- No widening of `Model.using()` to accept transaction handles, connection objects, role hints, or anything other than the existing `name: str`.
|
|
29
|
+
- No audit of unrelated typing erasure elsewhere in the ORM (relation descriptors, raw queries, query builder internals). Anything discovered during this work that is out-of-scope routes to deferred follow-up rather than expanding this plan.
|
|
30
|
+
- No changes to `Query`, `Model`, or any FFI signatures — `Query` is already generic and that infrastructure is sufficient.
|
|
31
|
+
- No new type-checker wiring into CI. The repo currently has no Pyright/mypy hook (see `.pre-commit-config.yaml` lines 45–51, type checking is commented out). Adding CI enforcement for static typing is a separate, larger discussion.
|
|
32
|
+
|
|
33
|
+
### Deferred to Follow-Up Work
|
|
34
|
+
|
|
35
|
+
- Wiring Pyright into pre-commit / CI so `assert_type` regressions fail builds: separate plan, larger scope (config, ignore lists, baseline noise).
|
|
36
|
+
- Audit of `relations/descriptors.py` and other Model-returning APIs for similar erasure: separate plan if/when surfaced.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Context & Research
|
|
41
|
+
|
|
42
|
+
### Relevant Code and Patterns
|
|
43
|
+
|
|
44
|
+
- `src/ferro/models.py` lines 452–455: `Model.using` classmethod, already annotated `-> ModelConnection[Self]` (the annotation is aspirational today — `ModelConnection` is not actually subscriptable).
|
|
45
|
+
- `src/ferro/models.py` lines 557–619: `ModelConnection` class definition. All eight methods to retype live here.
|
|
46
|
+
- `src/ferro/query/builder.py` line 29: `class Query(Generic[T])` — already generic. `select()` and `where()` should return `Query[M]` after this refactor; the constructor `Query(self.model_cls, using=...)` already infers `T` from the class argument.
|
|
47
|
+
- `src/ferro/relations/descriptors.py` line 112: `await self._target_model.using(origin).get(id_val)` — only external caller of `ModelConnection`'s instance-level methods we found in repo. Will benefit automatically from the typing fix; no change needed.
|
|
48
|
+
|
|
49
|
+
### Internal Usages of `self.using` (the instance attribute being renamed)
|
|
50
|
+
|
|
51
|
+
Confirmed via grep — all references are inside `ModelConnection`'s own method bodies:
|
|
52
|
+
|
|
53
|
+
- `models.py:566` — `await instance.save(using=self.using)`
|
|
54
|
+
- `models.py:570` — `using=self.using`
|
|
55
|
+
- `models.py:573` — `Query(..., using=self.using)`
|
|
56
|
+
- `models.py:588` — `bulk_create(..., using=self.using)`
|
|
57
|
+
- `models.py:593` — `Query(..., using=self.using)`
|
|
58
|
+
- `models.py:607` — `Query(..., using=self.using)`
|
|
59
|
+
- `models.py:615` — `await instance.save(using=self.using)`
|
|
60
|
+
|
|
61
|
+
No external code reads `instance.using` on a `ModelConnection` object. Renaming is safe.
|
|
62
|
+
|
|
63
|
+
### Institutional Learnings
|
|
64
|
+
|
|
65
|
+
- `.cursorrules` §4 — TDD workflow: write a failing test first, then implement. Applies here even though the "test" for the typing fix is a static `assert_type` rather than runtime assertion. The runtime regression test for the rename is conventional pytest.
|
|
66
|
+
- `AGENTS.md` I-3 — no `unwrap()` across FFI. Not applicable here (Python-only change), but reinforces "this should not need to touch Rust."
|
|
67
|
+
- `AGENTS.md` I-5 — `docs/solutions/` is institutional memory. Worth adding a short note under `docs/solutions/patterns/` if the PEP 695 generic syntax is the first instance in the codebase, so future agents have a reference.
|
|
68
|
+
|
|
69
|
+
### External References
|
|
70
|
+
|
|
71
|
+
- PEP 695 (Type Parameter Syntax, Python 3.12+) — Ferro's `requires-python = ">=3.13"` makes this the modern default over `Generic[M]`. No external lookup needed; the syntax is well-known.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Key Technical Decisions
|
|
76
|
+
|
|
77
|
+
- **Use PEP 695 syntax (`class ModelConnection[M: Model]:`) over `Generic[M]`.** Project pins Python 3.13+ (`pyproject.toml:9`), so the older syntax has no compatibility benefit and adds an import (`Generic`, `TypeVar`). PEP 695 is also what `Query` should eventually move to for consistency, but that migration is out of scope here.
|
|
78
|
+
- **Rename `self.using` to `self._connection_name`.** Underscore-prefix signals "internal — don't read this from outside the class," which matches how the attribute is actually used. Avoids the `Model.using` / `ModelConnection.using` name collision that prompted this work in the first place.
|
|
79
|
+
- **Test typing via `typing.assert_type` co-located with existing connection tests, not in a new `tests/typing/` directory.** No type checker runs in CI today, so a separate typing test directory would be a discoverability problem (no signal points at it). Inline `assert_type` calls in the existing test files make the intent visible to readers, run as no-ops at runtime, and can be picked up later if/when Pyright is wired into CI.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Open Questions
|
|
84
|
+
|
|
85
|
+
### Resolved During Planning
|
|
86
|
+
|
|
87
|
+
- *Should we use `Generic[M]` or PEP 695 `[M: Model]`?* — PEP 695, see Key Technical Decisions.
|
|
88
|
+
- *What to rename `self.using` to?* — `self._connection_name`, see Key Technical Decisions.
|
|
89
|
+
- *Do we need to update `src/ferro/_core.pyi`?* — No. `ModelConnection` is a pure-Python class; no FFI signatures cross.
|
|
90
|
+
|
|
91
|
+
### Deferred to Implementation
|
|
92
|
+
|
|
93
|
+
- *Does `Query`'s existing generic propagation flow through the new `M` cleanly when `ModelConnection.select()` returns `Query[M]`?* — Should "just work" since `Query(model_cls, ...)` infers `T` from `type[T]`, and after the refactor `self.model_cls: type[M]`. Verified at implementation time by writing the `assert_type` calls and running Pyright locally.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Implementation Units
|
|
98
|
+
|
|
99
|
+
- U1. **Add typing regression coverage for `.using()`**
|
|
100
|
+
|
|
101
|
+
**Goal:** Lock in the desired post-refactor types via `typing.assert_type` calls so the typing improvement is observable and won't silently regress.
|
|
102
|
+
|
|
103
|
+
**Requirements:** R1, R2
|
|
104
|
+
|
|
105
|
+
**Dependencies:** None.
|
|
106
|
+
|
|
107
|
+
**Files:**
|
|
108
|
+
- Modify: `tests/test_connection.py` (or `tests/test_named_connections_integration.py` — pick the file that already exercises the same model)
|
|
109
|
+
- Test: same file (typing assertions co-located with runtime tests)
|
|
110
|
+
|
|
111
|
+
**Approach:**
|
|
112
|
+
- Add a small block of `typing.assert_type(...)` calls against an existing test model (e.g., `ConnectionRouteMarker` or `NamedSmokeMarker`).
|
|
113
|
+
- Cover the full surface called out in R2: `create`, `all`, `select`, `where`, `bulk_create`, `get_or_create`, `update_or_create`, `get`.
|
|
114
|
+
- These are no-ops at runtime; Pyright/Pylance will flag any future regression. Document at the top of the block that they're for static type checking.
|
|
115
|
+
|
|
116
|
+
**Execution note:** Test-first per `.cursorrules` §4. Write these assertions before touching `models.py` — Pyright should report errors on every line until U2 lands.
|
|
117
|
+
|
|
118
|
+
**Patterns to follow:**
|
|
119
|
+
- Existing test file structure under `tests/`.
|
|
120
|
+
|
|
121
|
+
**Test scenarios:**
|
|
122
|
+
- Happy path: `assert_type(ConnectionRouteMarker.using("service"), ModelConnection[ConnectionRouteMarker])` — proves the classmethod's annotation is now truthful.
|
|
123
|
+
- Happy path: `assert_type(await ConnectionRouteMarker.using("service").get(1), ConnectionRouteMarker | None)` — covers R1 directly.
|
|
124
|
+
- Happy path: one `assert_type` per remaining method in R2 (`create`, `all`, `select`, `where`, `bulk_create`, `get_or_create`, `update_or_create`).
|
|
125
|
+
- Test expectation at runtime: these statements should execute without error. `assert_type` is a runtime no-op.
|
|
126
|
+
|
|
127
|
+
**Verification:**
|
|
128
|
+
- Before U2 lands: `uv run pytest` passes (assert_type doesn't fail at runtime), but Pyright on the test file reports type errors on each new line.
|
|
129
|
+
- After U2 lands: Pyright on the test file is clean.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
- U2. **Make `ModelConnection` generic and rename the connection-name attribute**
|
|
134
|
+
|
|
135
|
+
**Goal:** Parameterize `ModelConnection` over `M: Model` and rename `self.using` → `self._connection_name`.
|
|
136
|
+
|
|
137
|
+
**Requirements:** R1, R2, R3, R5
|
|
138
|
+
|
|
139
|
+
**Dependencies:** U1.
|
|
140
|
+
|
|
141
|
+
**Files:**
|
|
142
|
+
- Modify: `src/ferro/models.py` (lines 557–619 — the `ModelConnection` class body)
|
|
143
|
+
|
|
144
|
+
**Approach:**
|
|
145
|
+
- Change class header to `class ModelConnection[M: Model]:`.
|
|
146
|
+
- `__init__` signature: `model_cls: type[M], connection_name: str` (parameter name change is internal — callers go through `Model.using()` which positional-passes the string).
|
|
147
|
+
- `self.model_cls: type[M] = model_cls`; `self._connection_name: str = connection_name`.
|
|
148
|
+
- Replace all seven `self.using` references in method bodies with `self._connection_name`.
|
|
149
|
+
- Method return-type annotations:
|
|
150
|
+
- `create(...) -> M`
|
|
151
|
+
- `all() -> list[M]`
|
|
152
|
+
- `select() -> Query[M]`
|
|
153
|
+
- `where(node) -> Query[M]`
|
|
154
|
+
- `get(pk) -> M | None`
|
|
155
|
+
- `bulk_create(instances: list[M]) -> int`
|
|
156
|
+
- `get_or_create(...) -> tuple[M, bool]`
|
|
157
|
+
- `update_or_create(...) -> tuple[M, bool]`
|
|
158
|
+
- `Model.using` classmethod (lines 452–455) stays as-is — its `-> "ModelConnection[Self]"` annotation now resolves correctly.
|
|
159
|
+
|
|
160
|
+
**Patterns to follow:**
|
|
161
|
+
- `src/ferro/query/builder.py:29` (`class Query(Generic[T])`) for how a generic ORM-side class is wired, though we use the newer PEP 695 syntax here.
|
|
162
|
+
|
|
163
|
+
**Test scenarios:**
|
|
164
|
+
- Covers R4. Happy path: full existing `tests/test_connection.py` and `tests/test_named_connections_integration.py` suites pass unchanged. The rename is purely internal so no behavior changes.
|
|
165
|
+
- Covers R1, R2. Happy path: every `assert_type` call added in U1 type-checks clean under Pyright after this unit lands.
|
|
166
|
+
- Edge case: confirm that `Model.using("name").select().where(...).first()` chains all the way through — each link preserves `M`. Already covered indirectly by the U1 `select` and `where` assertions plus existing query tests.
|
|
167
|
+
|
|
168
|
+
**Verification:**
|
|
169
|
+
- `uv run pytest tests/test_connection.py tests/test_named_connections_integration.py` is green.
|
|
170
|
+
- `uv run maturin develop && uv run pytest` is green (full suite — confirms no relation-descriptor or other internal caller broke).
|
|
171
|
+
- Manually run Pyright on `tests/test_connection.py` (or whichever file got the U1 assertions): no errors on the typing block.
|
|
172
|
+
- Visual check: `Transcript.using(SERVICE).get("…")` in a fresh editor session shows `Transcript | None` in the hover tooltip.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Risks & Dependencies
|
|
177
|
+
|
|
178
|
+
| Risk | Mitigation |
|
|
179
|
+
|------|------------|
|
|
180
|
+
| Renaming `self.using` accidentally breaks an external caller we missed in the grep. | Pre-flight grep confirmed all references are internal to the class body. The full pytest suite runs in U2 verification. |
|
|
181
|
+
| `Query[M]` doesn't propagate as cleanly as expected because `Query`'s `__init__` infers `T` differently than expected from `type[M]`. | Verified at implementation time via the `assert_type(self.select(), Query[M])` assertion in U1. If it fails, fall back to an explicit cast or constructor annotation; document the fix in `docs/solutions/patterns/`. |
|
|
182
|
+
| The typing improvement degrades inside generic mixins or subclasses of `Model` due to `Self` interaction. | `Model.using` returns `ModelConnection[Self]`; this is the standard pattern and shouldn't fight `M: Model`. If a subclass-specific issue surfaces, scope the fix to that subclass — don't expand U2. |
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Sources & References
|
|
187
|
+
|
|
188
|
+
- Brainstorm conversation in this session establishing scope (generic + rename, no broader audit).
|
|
189
|
+
- `src/ferro/models.py:452-619` — current `Model.using` and `ModelConnection` definitions.
|
|
190
|
+
- `src/ferro/query/builder.py:29` — existing generic ORM class as reference pattern.
|
|
191
|
+
- `pyproject.toml:9` — `requires-python = ">=3.13"`, justifying PEP 695 syntax.
|
|
192
|
+
- PEP 695 — Type Parameter Syntax.
|
|
@@ -30,6 +30,8 @@ impl BackendKind {
|
|
|
30
30
|
pub struct EngineHandle {
|
|
31
31
|
backend: BackendKind,
|
|
32
32
|
pool: BackendPool,
|
|
33
|
+
/// When false, Ferro skips the identity map for this connection (no lookup/register on load).
|
|
34
|
+
identity_map_enabled: bool,
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
#[derive(Clone, Debug)]
|
|
@@ -116,6 +118,7 @@ impl EngineHandle {
|
|
|
116
118
|
Self {
|
|
117
119
|
backend: BackendKind::Sqlite,
|
|
118
120
|
pool: BackendPool::Sqlite(Arc::new(pool)),
|
|
121
|
+
identity_map_enabled: true,
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -123,9 +126,23 @@ impl EngineHandle {
|
|
|
123
126
|
Self {
|
|
124
127
|
backend: BackendKind::Postgres,
|
|
125
128
|
pool: BackendPool::Postgres(Arc::new(pool)),
|
|
129
|
+
identity_map_enabled: true,
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
/// Returns whether this connection uses the identity map (singleton instances per PK).
|
|
134
|
+
#[must_use]
|
|
135
|
+
pub fn is_identity_map_enabled(&self) -> bool {
|
|
136
|
+
self.identity_map_enabled
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Sets identity-map behavior for this handle (used by `connect(identity_map=...)`).
|
|
140
|
+
#[must_use]
|
|
141
|
+
pub fn with_identity_map_enabled(mut self, enabled: bool) -> Self {
|
|
142
|
+
self.identity_map_enabled = enabled;
|
|
143
|
+
self
|
|
144
|
+
}
|
|
145
|
+
|
|
129
146
|
pub fn backend(&self) -> BackendKind {
|
|
130
147
|
self.backend
|
|
131
148
|
}
|
|
@@ -187,7 +187,7 @@ async fn connect_engine_handle(
|
|
|
187
187
|
/// # Errors
|
|
188
188
|
/// Returns a `PyErr` if the connection fails or if auto-migration fails.
|
|
189
189
|
#[pyfunction]
|
|
190
|
-
#[pyo3(signature = (url, auto_migrate=false, name=None, default=false, max_connections=5, min_connections=0))]
|
|
190
|
+
#[pyo3(signature = (url, auto_migrate=false, name=None, default=false, max_connections=5, min_connections=0, identity_map=true))]
|
|
191
191
|
pub fn connect(
|
|
192
192
|
py: Python<'_>,
|
|
193
193
|
url: String,
|
|
@@ -196,6 +196,7 @@ pub fn connect(
|
|
|
196
196
|
default: bool,
|
|
197
197
|
max_connections: u32,
|
|
198
198
|
min_connections: u32,
|
|
199
|
+
identity_map: bool,
|
|
199
200
|
) -> PyResult<Bound<'_, PyAny>> {
|
|
200
201
|
let (connection_url, search_path) = split_search_path(&url);
|
|
201
202
|
let redacted_url = redact_connection_url(&connection_url);
|
|
@@ -247,7 +248,8 @@ pub fn connect(
|
|
|
247
248
|
"DB Connection failed for {}: {}",
|
|
248
249
|
redacted_url, e
|
|
249
250
|
))
|
|
250
|
-
})
|
|
251
|
+
})?
|
|
252
|
+
.with_identity_map_enabled(identity_map);
|
|
251
253
|
|
|
252
254
|
let engine_handle = Arc::new(engine_handle);
|
|
253
255
|
|
|
@@ -60,6 +60,8 @@ async def connect(
|
|
|
60
60
|
name: str | None = None,
|
|
61
61
|
default: bool = False,
|
|
62
62
|
pool: PoolConfig | None = None,
|
|
63
|
+
*,
|
|
64
|
+
identity_map: bool = True,
|
|
63
65
|
) -> None:
|
|
64
66
|
"""
|
|
65
67
|
Establish a connection to the database.
|
|
@@ -70,6 +72,9 @@ async def connect(
|
|
|
70
72
|
name: Optional connection name. Omitted connections register as "default".
|
|
71
73
|
default: If True, make this named connection the default for unqualified operations.
|
|
72
74
|
pool: Optional per-connection pool configuration.
|
|
75
|
+
identity_map: If True (default), keep a per-connection identity map so the same primary
|
|
76
|
+
key maps to a single Python instance. If False, each load returns fresh instances and
|
|
77
|
+
the map is not consulted (lower memory use; no ``a is b`` guarantees across loads).
|
|
73
78
|
"""
|
|
74
79
|
from .relations import resolve_relationships
|
|
75
80
|
|
|
@@ -83,6 +88,7 @@ async def connect(
|
|
|
83
88
|
default=default,
|
|
84
89
|
max_connections=pool_config.max_connections,
|
|
85
90
|
min_connections=pool_config.min_connections,
|
|
91
|
+
identity_map=identity_map,
|
|
86
92
|
)
|
|
87
93
|
|
|
88
94
|
|
|
@@ -554,28 +554,33 @@ class Model(BaseModel, metaclass=ModelMetaclass):
|
|
|
554
554
|
return await cls.create(**params), True
|
|
555
555
|
|
|
556
556
|
|
|
557
|
-
class ModelConnection:
|
|
558
|
-
"""Connection-bound ORM entrypoint returned by ``Model.using(name)``.
|
|
557
|
+
class ModelConnection[M: Model]:
|
|
558
|
+
"""Connection-bound ORM entrypoint returned by ``Model.using(name)``.
|
|
559
559
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
560
|
+
Generic over the concrete model class so that every accessor preserves
|
|
561
|
+
the bound type — e.g. ``Transcript.using("service").get(pk)`` resolves
|
|
562
|
+
to ``Transcript | None`` rather than ``Model | None``.
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
def __init__(self, model_cls: type[M], connection_name: str) -> None:
|
|
566
|
+
self.model_cls: type[M] = model_cls
|
|
567
|
+
self._connection_name: str = connection_name
|
|
563
568
|
|
|
564
|
-
async def create(self, **fields: Any) ->
|
|
569
|
+
async def create(self, **fields: Any) -> M:
|
|
565
570
|
instance = self.model_cls(**fields)
|
|
566
|
-
await instance.save(using=self.
|
|
571
|
+
await instance.save(using=self._connection_name)
|
|
567
572
|
return instance
|
|
568
573
|
|
|
569
|
-
async def all(self) -> list[
|
|
570
|
-
return await self.model_cls.all(using=self.
|
|
574
|
+
async def all(self) -> list[M]:
|
|
575
|
+
return await self.model_cls.all(using=self._connection_name)
|
|
571
576
|
|
|
572
|
-
def select(self) -> Query[
|
|
573
|
-
return Query(self.model_cls, using=self.
|
|
577
|
+
def select(self) -> Query[M]:
|
|
578
|
+
return Query(self.model_cls, using=self._connection_name)
|
|
574
579
|
|
|
575
|
-
def where(self, node: QueryNode) -> Query[
|
|
580
|
+
def where(self, node: QueryNode) -> Query[M]:
|
|
576
581
|
return self.select().where(node)
|
|
577
582
|
|
|
578
|
-
async def get(self, pk: Any) ->
|
|
583
|
+
async def get(self, pk: Any) -> M | None:
|
|
579
584
|
pk_field_name = self.model_cls._primary_key_field_name()
|
|
580
585
|
if pk_field_name is None:
|
|
581
586
|
raise RuntimeError(
|
|
@@ -584,13 +589,13 @@ class ModelConnection:
|
|
|
584
589
|
|
|
585
590
|
return await self.where(getattr(self.model_cls, pk_field_name) == pk).first()
|
|
586
591
|
|
|
587
|
-
async def bulk_create(self, instances: list[
|
|
588
|
-
return await self.model_cls.bulk_create(instances, using=self.
|
|
592
|
+
async def bulk_create(self, instances: list[M]) -> int:
|
|
593
|
+
return await self.model_cls.bulk_create(instances, using=self._connection_name)
|
|
589
594
|
|
|
590
595
|
async def get_or_create(
|
|
591
596
|
self, defaults: dict[str, Any] | None = None, **fields: Any
|
|
592
|
-
) -> tuple[
|
|
593
|
-
query = Query(self.model_cls, using=self.
|
|
597
|
+
) -> tuple[M, bool]:
|
|
598
|
+
query = Query(self.model_cls, using=self._connection_name)
|
|
594
599
|
for key, val in fields.items():
|
|
595
600
|
query = query.where(getattr(self.model_cls, key) == val)
|
|
596
601
|
|
|
@@ -603,8 +608,8 @@ class ModelConnection:
|
|
|
603
608
|
|
|
604
609
|
async def update_or_create(
|
|
605
610
|
self, defaults: dict[str, Any] | None = None, **fields: Any
|
|
606
|
-
) -> tuple[
|
|
607
|
-
query = Query(self.model_cls, using=self.
|
|
611
|
+
) -> tuple[M, bool]:
|
|
612
|
+
query = Query(self.model_cls, using=self._connection_name)
|
|
608
613
|
for key, val in fields.items():
|
|
609
614
|
query = query.where(getattr(self.model_cls, key) == val)
|
|
610
615
|
|
|
@@ -612,7 +617,7 @@ class ModelConnection:
|
|
|
612
617
|
if instance:
|
|
613
618
|
for key, val in (defaults or {}).items():
|
|
614
619
|
setattr(instance, key, val)
|
|
615
|
-
await instance.save(using=self.
|
|
620
|
+
await instance.save(using=self._connection_name)
|
|
616
621
|
return instance, False
|
|
617
622
|
|
|
618
623
|
params = {**fields, **(defaults or {})}
|