ferro-orm 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/workflows/ci.yml +56 -1
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/CHANGELOG.md +51 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/Cargo.lock +7 -7
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/Cargo.toml +1 -1
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/PKG-INFO +3 -1
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/README.md +2 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/api/utilities.md +5 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/concepts/performance.md +3 -6
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/faq.md +4 -7
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/getting-started/installation.md +4 -8
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/database.md +13 -35
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/models-and-fields.md +1 -1
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/howto/multiple-databases.md +1 -1
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/howto/testing.md +66 -9
- ferro_orm-0.3.3/docs/plans/2026-04-24-001-refactor-multi-db-backend-architecture-plan.md +473 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/pyproject.toml +3 -1
- ferro_orm-0.3.3/src/backend.rs +108 -0
- ferro_orm-0.3.3/src/connection.rs +128 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/_core.pyi +4 -2
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/_shadow_fk_types.py +30 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/metaclass.py +2 -48
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/migrations/alembic.py +2 -45
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/models.py +29 -31
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/query/nodes.py +14 -7
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/relations/__init__.py +9 -36
- ferro_orm-0.3.3/src/ferro/schema_metadata.py +138 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/lib.rs +1 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/operations.rs +733 -210
- ferro_orm-0.3.3/src/query.rs +282 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/schema.rs +141 -34
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/state.rs +49 -4
- ferro_orm-0.3.3/tests/__init__.py +1 -0
- ferro_orm-0.3.3/tests/conftest.py +182 -0
- ferro_orm-0.3.3/tests/db_backends.py +67 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_aggregation.py +1 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_alembic_bridge.py +23 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_alembic_nullability.py +1 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_auto_migrate.py +8 -6
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_bulk_update.py +1 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_composite_unique.py +61 -13
- ferro_orm-0.3.3/tests/test_connection.py +37 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_constraints.py +33 -14
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_crud.py +4 -18
- ferro_orm-0.3.3/tests/test_db_backends.py +77 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_deletion.py +1 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_documentation_features.py +3 -15
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_field_wrapper.py +15 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_helpers.py +1 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_hydration.py +1 -11
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_metadata.py +1 -9
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_one_to_one.py +60 -20
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_query_builder.py +1 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_refresh.py +1 -10
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_schema.py +4 -3
- ferro_orm-0.3.3/tests/test_schema_constraints.py +137 -0
- ferro_orm-0.3.3/tests/test_schema_enum_annotations.py +26 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_shadow_fk_types.py +10 -8
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_string_search.py +1 -10
- ferro_orm-0.3.3/tests/test_structural_types.py +277 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_temporal_types.py +1 -10
- ferro_orm-0.3.3/tests/test_transactions.py +161 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/uv.lock +51 -1
- ferro_orm-0.3.2/src/connection.rs +0 -68
- ferro_orm-0.3.2/src/query.rs +0 -112
- ferro_orm-0.3.2/tests/conftest.py +0 -48
- ferro_orm-0.3.2/tests/test_connection.py +0 -27
- ferro_orm-0.3.2/tests/test_schema_constraints.py +0 -69
- ferro_orm-0.3.2/tests/test_structural_types.py +0 -106
- ferro_orm-0.3.2/tests/test_transactions.py +0 -93
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.github/workflows/release.yml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.gitignore +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/.python-version +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/LICENSE +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/TEST_RESULTS.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/api/fields.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/api/model.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/api/query.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/api/relationships.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/api/transactions.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/changelog.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/coming-soon.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/concepts/architecture.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/concepts/identity-map.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/concepts/type-safety.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/contributing.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/getting-started/next-steps.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/getting-started/tutorial.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/migrations.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/mutations.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/queries.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/relationships.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/guide/transactions.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/howto/pagination.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/howto/soft-deletes.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/howto/timestamps.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/index.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/migration-sqlalchemy.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/stylesheets/extra.css +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/docs/why-ferro.md +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/justfile +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/mkdocs.yml +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/__init__.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/base.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/fields.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/py.typed +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/query/__init__.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/query/builder.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/relations/descriptors.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/src/ferro/state.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_models.py +0 -0
- {ferro_orm-0.3.2 → ferro_orm-0.3.3}/tests/test_relationship_engine.py +0 -0
|
@@ -171,6 +171,60 @@ jobs:
|
|
|
171
171
|
name: python-${{ matrix.python-version }}
|
|
172
172
|
continue-on-error: true
|
|
173
173
|
|
|
174
|
+
test-python-backend-matrix:
|
|
175
|
+
name: Python backend matrix (SQLite + Postgres)
|
|
176
|
+
runs-on: ubuntu-latest
|
|
177
|
+
services:
|
|
178
|
+
postgres:
|
|
179
|
+
image: postgres:17
|
|
180
|
+
env:
|
|
181
|
+
POSTGRES_USER: ferro
|
|
182
|
+
POSTGRES_PASSWORD: ferro
|
|
183
|
+
POSTGRES_DB: ferro
|
|
184
|
+
ports:
|
|
185
|
+
- 5432:5432
|
|
186
|
+
options: >-
|
|
187
|
+
--health-cmd "pg_isready -U ferro -d ferro"
|
|
188
|
+
--health-interval 10s
|
|
189
|
+
--health-timeout 5s
|
|
190
|
+
--health-retries 5
|
|
191
|
+
env:
|
|
192
|
+
FERRO_SUPABASE_URL: postgresql://ferro:ferro@127.0.0.1:5432/ferro?sslmode=disable
|
|
193
|
+
steps:
|
|
194
|
+
- name: Checkout repository
|
|
195
|
+
uses: actions/checkout@v4
|
|
196
|
+
|
|
197
|
+
- name: Set up Python
|
|
198
|
+
uses: actions/setup-python@v5
|
|
199
|
+
with:
|
|
200
|
+
python-version: '3.13'
|
|
201
|
+
|
|
202
|
+
- name: Install UV
|
|
203
|
+
uses: astral-sh/setup-uv@v5
|
|
204
|
+
with:
|
|
205
|
+
enable-cache: true
|
|
206
|
+
|
|
207
|
+
- name: Set up Rust
|
|
208
|
+
uses: dtolnay/rust-toolchain@stable
|
|
209
|
+
|
|
210
|
+
- name: Cache Rust build
|
|
211
|
+
uses: Swatinem/rust-cache@v2
|
|
212
|
+
with:
|
|
213
|
+
prefix-key: v1
|
|
214
|
+
cache-on-failure: true
|
|
215
|
+
|
|
216
|
+
- name: Install dependencies
|
|
217
|
+
run: |
|
|
218
|
+
uv sync --only-group ci-test --no-install-project --python 3.13
|
|
219
|
+
|
|
220
|
+
- name: Build Rust extension
|
|
221
|
+
run: |
|
|
222
|
+
uv run maturin develop
|
|
223
|
+
|
|
224
|
+
- name: Run backend matrix tests
|
|
225
|
+
run: |
|
|
226
|
+
uv run pytest -v -m "backend_matrix or postgres_only" --db-backends=sqlite,postgres
|
|
227
|
+
|
|
174
228
|
check-conventional-commits:
|
|
175
229
|
name: Check Conventional Commits
|
|
176
230
|
runs-on: ubuntu-latest
|
|
@@ -220,7 +274,7 @@ jobs:
|
|
|
220
274
|
|
|
221
275
|
all-checks:
|
|
222
276
|
name: All Checks Passed
|
|
223
|
-
needs: [lint-and-format, test-python-pr, test-python-main, test-rust]
|
|
277
|
+
needs: [lint-and-format, test-python-pr, test-python-main, test-python-backend-matrix, test-rust]
|
|
224
278
|
runs-on: ubuntu-latest
|
|
225
279
|
if: always()
|
|
226
280
|
steps:
|
|
@@ -232,6 +286,7 @@ jobs:
|
|
|
232
286
|
if ! ok "${{ needs.lint-and-format.result }}"; then exit 1; fi
|
|
233
287
|
if ! ok "${{ needs.test-python-pr.result }}"; then exit 1; fi
|
|
234
288
|
if ! ok "${{ needs.test-python-main.result }}"; then exit 1; fi
|
|
289
|
+
if ! ok "${{ needs.test-python-backend-matrix.result }}"; then exit 1; fi
|
|
235
290
|
if ! ok "${{ needs.test-rust.result }}"; then exit 1; fi
|
|
236
291
|
|
|
237
292
|
echo "All checks passed!"
|
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.3.3 (2026-04-24)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Cast NULL and strings to ::uuid for Postgres using catalog
|
|
9
|
+
([`f5cb4f0`](https://github.com/syn54x/ferro-orm/commit/f5cb4f08ceaf0763a29c3b78d4d077ca1119fc1c))
|
|
10
|
+
|
|
11
|
+
- Catalog casts for date/timestamp columns on Postgres
|
|
12
|
+
([`95ef5ca`](https://github.com/syn54x/ferro-orm/commit/95ef5cadc28eb26481c38b51dbca1b370a883d10))
|
|
13
|
+
|
|
14
|
+
- Clean up rebase conflicts with main
|
|
15
|
+
([`716511c`](https://github.com/syn54x/ferro-orm/commit/716511c829021ee6d2390bb85c877e670c1d7631))
|
|
16
|
+
|
|
17
|
+
- Enum OIDs
|
|
18
|
+
([`a9867be`](https://github.com/syn54x/ferro-orm/commit/a9867beac242a9d630aeb7e49b718a4234c541ec))
|
|
19
|
+
|
|
20
|
+
- Postgres native enums on save and StrEnum schema registration
|
|
21
|
+
([`44277e1`](https://github.com/syn54x/ferro-orm/commit/44277e1922182b020c17d9a7a2a9e99dd62061e5))
|
|
22
|
+
|
|
23
|
+
- Use Postgres SQL dialect when connecting to postgres URLs
|
|
24
|
+
([`c627ac8`](https://github.com/syn54x/ferro-orm/commit/c627ac8e4fa84555e0cc7250f73ce6f0858125a3))
|
|
25
|
+
|
|
26
|
+
- **postgres**: Add dual-db ORM test matrix
|
|
27
|
+
([`1fa657f`](https://github.com/syn54x/ferro-orm/commit/1fa657fe4335d41214fcb24b1eac5dcf3138273f))
|
|
28
|
+
|
|
29
|
+
- **postgres**: Bind boolean writes as booleans
|
|
30
|
+
([`346441a`](https://github.com/syn54x/ferro-orm/commit/346441a073a540c857a8aaa67bf4029cb4099535))
|
|
31
|
+
|
|
32
|
+
- **postgres**: Cast uuid columns to text in SELECT for Any decode
|
|
33
|
+
([`df957c0`](https://github.com/syn54x/ferro-orm/commit/df957c0202d32608843d6a24ae4c924ed5b9381d))
|
|
34
|
+
|
|
35
|
+
- **postgres**: Cast UUID filter params for sqlx Any compatibility
|
|
36
|
+
([`889cf8b`](https://github.com/syn54x/ferro-orm/commit/889cf8b61131c2d53e8414a76ca7b2dbc7868c23))
|
|
37
|
+
|
|
38
|
+
- **postgres**: Decode native enum columns via text cast
|
|
39
|
+
([`1270f9d`](https://github.com/syn54x/ferro-orm/commit/1270f9dcd1cc5aa19cf484c3d9c3bb3a82255a05))
|
|
40
|
+
|
|
41
|
+
### Refactoring
|
|
42
|
+
|
|
43
|
+
- Expand db matrix coverage and harden postgres paths
|
|
44
|
+
([`b82f3ac`](https://github.com/syn54x/ferro-orm/commit/b82f3ac886459861cdfde122b99b880b85c09a61))
|
|
45
|
+
|
|
46
|
+
- Multi db architecture with true sqlite and postgres support
|
|
47
|
+
([`459a0c5`](https://github.com/syn54x/ferro-orm/commit/459a0c5f9c8a95ecacc9ba552137252d34de4824))
|
|
48
|
+
|
|
49
|
+
### Testing
|
|
50
|
+
|
|
51
|
+
- Expand schema constraints into db matrix
|
|
52
|
+
([`24a7f0a`](https://github.com/syn54x/ferro-orm/commit/24a7f0ad38b90e98a41cf32fe2777d988ff7047f))
|
|
53
|
+
|
|
54
|
+
|
|
4
55
|
## v0.3.2 (2026-04-24)
|
|
5
56
|
|
|
6
57
|
### Bug Fixes
|
|
@@ -79,9 +79,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|
|
79
79
|
|
|
80
80
|
[[package]]
|
|
81
81
|
name = "cc"
|
|
82
|
-
version = "1.2.
|
|
82
|
+
version = "1.2.61"
|
|
83
83
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
84
|
-
checksum = "
|
|
84
|
+
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
|
85
85
|
dependencies = [
|
|
86
86
|
"find-msvc-tools",
|
|
87
87
|
"shlex",
|
|
@@ -128,9 +128,9 @@ dependencies = [
|
|
|
128
128
|
|
|
129
129
|
[[package]]
|
|
130
130
|
name = "crc-catalog"
|
|
131
|
-
version = "2.
|
|
131
|
+
version = "2.5.0"
|
|
132
132
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
-
checksum = "
|
|
133
|
+
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
|
134
134
|
|
|
135
135
|
[[package]]
|
|
136
136
|
name = "crossbeam-queue"
|
|
@@ -294,7 +294,7 @@ dependencies = [
|
|
|
294
294
|
|
|
295
295
|
[[package]]
|
|
296
296
|
name = "ferro"
|
|
297
|
-
version = "0.3.
|
|
297
|
+
version = "0.3.3"
|
|
298
298
|
dependencies = [
|
|
299
299
|
"dashmap",
|
|
300
300
|
"once_cell",
|
|
@@ -1191,9 +1191,9 @@ dependencies = [
|
|
|
1191
1191
|
|
|
1192
1192
|
[[package]]
|
|
1193
1193
|
name = "rustls-pki-types"
|
|
1194
|
-
version = "1.14.
|
|
1194
|
+
version = "1.14.1"
|
|
1195
1195
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1196
|
-
checksum = "
|
|
1196
|
+
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
|
1197
1197
|
dependencies = [
|
|
1198
1198
|
"zeroize",
|
|
1199
1199
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ferro-orm
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Requires-Dist: pydantic>=2.0
|
|
5
5
|
Requires-Dist: alembic>=1.18.1 ; extra == 'alembic'
|
|
6
6
|
Requires-Dist: sqlalchemy>=2.0.46 ; extra == 'alembic'
|
|
@@ -54,6 +54,8 @@ pip install ferro-orm
|
|
|
54
54
|
pip install "ferro-orm[alembic]"
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
Ferro currently supports SQLite and PostgreSQL as runtime backends. Named multi-database routing and custom connection-pool kwargs are planned, but not part of the current public API.
|
|
58
|
+
|
|
57
59
|
## Quick Start
|
|
58
60
|
|
|
59
61
|
```python
|
|
@@ -41,6 +41,8 @@ pip install ferro-orm
|
|
|
41
41
|
pip install "ferro-orm[alembic]"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Ferro currently supports SQLite and PostgreSQL as runtime backends. Named multi-database routing and custom connection-pool kwargs are planned, but not part of the current public API.
|
|
45
|
+
|
|
44
46
|
## Quick Start
|
|
45
47
|
|
|
46
48
|
```python
|
|
@@ -17,24 +17,19 @@ await connect("sqlite:example.db?mode=rwc")
|
|
|
17
17
|
# PostgreSQL
|
|
18
18
|
await connect("postgresql://user:password@localhost/dbname")
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
await connect(
|
|
22
|
-
"postgresql://localhost/dbname",
|
|
23
|
-
max_connections=20,
|
|
24
|
-
auto_migrate=True # Development only
|
|
25
|
-
)
|
|
20
|
+
# Auto-migrate during development
|
|
21
|
+
await connect("postgresql://localhost/dbname", auto_migrate=True)
|
|
26
22
|
```
|
|
27
23
|
|
|
28
24
|
See [Database Setup Guide](../guide/database.md) for complete connection options.
|
|
29
25
|
|
|
30
26
|
### disconnect()
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
This function is not implemented yet.
|
|
33
29
|
|
|
34
30
|
```python
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await disconnect()
|
|
31
|
+
# Current pattern: connect once during startup
|
|
32
|
+
await connect("sqlite:example.db?mode=rwc")
|
|
38
33
|
```
|
|
39
34
|
|
|
40
35
|
### create_tables()
|
|
@@ -137,14 +137,11 @@ for post in posts:
|
|
|
137
137
|
posts = await Post.select().prefetch_related("author").all()
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
### 6.
|
|
140
|
+
### 6. Reuse a Long-Lived Connection
|
|
141
141
|
|
|
142
142
|
```python
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
max_connections=50, # Tune for your load
|
|
146
|
-
min_connections=10
|
|
147
|
-
)
|
|
143
|
+
# Current API: connect once during startup and reuse it.
|
|
144
|
+
await ferro.connect("postgresql://localhost/db")
|
|
148
145
|
```
|
|
149
146
|
|
|
150
147
|
### 7. Keep Transactions Short
|
|
@@ -137,7 +137,7 @@ Check your Ferro version's API for raw SQL support. Most versions provide an esc
|
|
|
137
137
|
|
|
138
138
|
### Does Ferro support multiple databases?
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
Not yet. Ferro currently supports a single active database connection per application process.
|
|
141
141
|
|
|
142
142
|
See [How-To: Multiple Databases](howto/multiple-databases.md).
|
|
143
143
|
|
|
@@ -198,14 +198,11 @@ Check the error message for details.
|
|
|
198
198
|
### How do I reset the database?
|
|
199
199
|
|
|
200
200
|
```python
|
|
201
|
-
#
|
|
202
|
-
await ferro.
|
|
203
|
-
|
|
204
|
-
# Recreate
|
|
205
|
-
await ferro.create_tables()
|
|
201
|
+
# Reconnect to a fresh SQLite test database
|
|
202
|
+
await ferro.connect("sqlite::memory:", auto_migrate=True)
|
|
206
203
|
```
|
|
207
204
|
|
|
208
|
-
|
|
205
|
+
For persistent environments, use Alembic migrations:
|
|
209
206
|
|
|
210
207
|
```bash
|
|
211
208
|
alembic downgrade base
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
- Python 3.10 or higher
|
|
6
6
|
- Supported platforms: macOS, Linux, Windows
|
|
7
|
-
- Database: SQLite
|
|
7
|
+
- Database: SQLite or PostgreSQL
|
|
8
8
|
|
|
9
9
|
## Install Ferro
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ This installs Alembic and SQLAlchemy (used only for migration generation, not at
|
|
|
30
30
|
|
|
31
31
|
## Database Drivers
|
|
32
32
|
|
|
33
|
-
Ferro uses SQLx under the hood
|
|
33
|
+
Ferro uses SQLx under the hood. SQLite and PostgreSQL support are built into Ferro's published packages, so no additional database-specific packages are required for those backends.
|
|
34
34
|
|
|
35
35
|
### SQLite
|
|
36
36
|
|
|
@@ -40,10 +40,6 @@ No additional setup needed. SQLite is embedded in Ferro.
|
|
|
40
40
|
|
|
41
41
|
No additional setup needed. PostgreSQL support is built into Ferro.
|
|
42
42
|
|
|
43
|
-
### MySQL
|
|
44
|
-
|
|
45
|
-
No additional setup needed. MySQL/MariaDB support is built into Ferro.
|
|
46
|
-
|
|
47
43
|
|
|
48
44
|
## Optional Dependencies
|
|
49
45
|
|
|
@@ -52,10 +48,10 @@ No additional setup needed. MySQL/MariaDB support is built into Ferro.
|
|
|
52
48
|
For running tests and linting:
|
|
53
49
|
|
|
54
50
|
```bash
|
|
55
|
-
|
|
51
|
+
uv sync --group dev
|
|
56
52
|
```
|
|
57
53
|
|
|
58
|
-
This includes pytest,
|
|
54
|
+
This workspace group includes pytest, maturin, docs tooling, and other development dependencies used in this repository.
|
|
59
55
|
|
|
60
56
|
## Building from Source
|
|
61
57
|
|
|
@@ -15,7 +15,7 @@ async def main():
|
|
|
15
15
|
|
|
16
16
|
## Connection Strings
|
|
17
17
|
|
|
18
|
-
Ferro supports SQLite
|
|
18
|
+
Ferro currently supports SQLite and PostgreSQL. The connection string format follows standard URL patterns:
|
|
19
19
|
|
|
20
20
|
### SQLite
|
|
21
21
|
|
|
@@ -45,10 +45,10 @@ await ferro.connect("postgresql://user:password@localhost:5432/dbname")
|
|
|
45
45
|
# With SSL
|
|
46
46
|
await ferro.connect("postgresql://user:password@localhost:5432/dbname?sslmode=require")
|
|
47
47
|
|
|
48
|
-
#
|
|
48
|
+
# Development connection with auto-migrate
|
|
49
49
|
await ferro.connect(
|
|
50
50
|
"postgresql://user:password@localhost:5432/dbname",
|
|
51
|
-
|
|
51
|
+
auto_migrate=True,
|
|
52
52
|
)
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -83,16 +83,6 @@ Supabase’s pooler hostname often looks like `*.pooler.supabase.com`; the datab
|
|
|
83
83
|
|
|
84
84
|
If you assemble the URI yourself, percent-encode reserved characters in the password (for example `%24` for `$`, `%5E` for `^`) per [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) userinfo rules. Many drivers accept unencoded passwords until one character breaks parsing; encoding avoids surprises.
|
|
85
85
|
|
|
86
|
-
### MySQL
|
|
87
|
-
|
|
88
|
-
```python
|
|
89
|
-
# Basic connection
|
|
90
|
-
await ferro.connect("mysql://user:password@localhost:3306/dbname")
|
|
91
|
-
|
|
92
|
-
# With charset
|
|
93
|
-
await ferro.connect("mysql://user:password@localhost:3306/dbname?charset=utf8mb4")
|
|
94
|
-
```
|
|
95
|
-
|
|
96
86
|
## Connection Options
|
|
97
87
|
|
|
98
88
|
### Auto-Migration (Development)
|
|
@@ -166,8 +156,7 @@ DATABASE_URL = os.getenv(
|
|
|
166
156
|
async def init_db():
|
|
167
157
|
await connect(
|
|
168
158
|
DATABASE_URL,
|
|
169
|
-
|
|
170
|
-
connect_timeout=int(os.getenv("DB_TIMEOUT", "30"))
|
|
159
|
+
auto_migrate=os.getenv("ENV") != "production"
|
|
171
160
|
)
|
|
172
161
|
```
|
|
173
162
|
|
|
@@ -207,12 +196,12 @@ async def on_shutdown():
|
|
|
207
196
|
!!! note "disconnect() Not Available"
|
|
208
197
|
The `disconnect()` function is not yet implemented. Connection cleanup happens automatically on process exit. See [Coming Soon](../coming-soon.md#disconnect) for more information.
|
|
209
198
|
|
|
210
|
-
### Use Connection
|
|
199
|
+
### Use One Long-Lived Connection
|
|
211
200
|
|
|
212
201
|
!!! note
|
|
213
|
-
Advanced
|
|
202
|
+
Advanced pool configuration such as `max_connections`, `min_connections`, and `connect_timeout` is not exposed by Ferro's current Python API. See [Coming Soon](../coming-soon.md#connection-pool-configuration).
|
|
214
203
|
|
|
215
|
-
For web applications
|
|
204
|
+
For web applications, connect once at startup and reuse that engine:
|
|
216
205
|
|
|
217
206
|
```python
|
|
218
207
|
# Basic connection for production
|
|
@@ -225,10 +214,7 @@ await ferro.connect("postgresql://localhost/proddb")
|
|
|
225
214
|
import os
|
|
226
215
|
|
|
227
216
|
if os.getenv("ENV") == "production":
|
|
228
|
-
await ferro.connect(
|
|
229
|
-
"postgresql://prodhost/proddb",
|
|
230
|
-
max_connections=50
|
|
231
|
-
)
|
|
217
|
+
await ferro.connect("postgresql://prodhost/proddb")
|
|
232
218
|
else:
|
|
233
219
|
await ferro.connect(
|
|
234
220
|
"sqlite:dev.db?mode=rwc",
|
|
@@ -255,7 +241,6 @@ except Exception as e:
|
|
|
255
241
|
# Error: Connection refused at localhost:5432
|
|
256
242
|
# Solution: Check database is running
|
|
257
243
|
# PostgreSQL: sudo service postgresql start
|
|
258
|
-
# MySQL: sudo service mysql start
|
|
259
244
|
```
|
|
260
245
|
|
|
261
246
|
### Authentication Failed
|
|
@@ -285,22 +270,15 @@ Ferro’s default build enables PostgreSQL TLS via SQLx (`tls-rustls-ring-webpki
|
|
|
285
270
|
|
|
286
271
|
If the server requires TLS but the URL omits it, add `?sslmode=require` (or `&sslmode=require` after other query parameters) as shown in the Supabase subsection above.
|
|
287
272
|
|
|
288
|
-
###
|
|
273
|
+
### Unsupported connect() kwargs
|
|
289
274
|
|
|
290
275
|
```python
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
await ferro.connect(
|
|
294
|
-
"postgresql://localhost/dbname",
|
|
295
|
-
max_connections=100 # Increase pool size
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
# Also ensure connections are released:
|
|
299
|
-
# - Use context managers (async with)
|
|
300
|
-
# - Close connections after use
|
|
301
|
-
# - Fix stuck transactions
|
|
276
|
+
# Example of kwargs Ferro does not currently accept:
|
|
277
|
+
# await ferro.connect("postgresql://localhost/dbname", max_connections=100)
|
|
302
278
|
```
|
|
303
279
|
|
|
280
|
+
If you need custom pool sizing or timeout controls today, Ferro does not expose them yet through `connect()`.
|
|
281
|
+
|
|
304
282
|
## See Also
|
|
305
283
|
|
|
306
284
|
- [Schema Management](migrations.md) - Alembic migrations
|
|
@@ -155,7 +155,7 @@ class OrgMembership(Model):
|
|
|
155
155
|
- You can list several groups for multiple composite uniques on one model.
|
|
156
156
|
- Invalid or unknown column names raise when the model is registered.
|
|
157
157
|
|
|
158
|
-
**Null semantics (SQLite):** With the default local SQLite engine, `UNIQUE` treats `NULL` as distinct from other `NULL` values for multi-column constraints unless columns are `NOT NULL`. Ferro maps nullability from your types and defaults like other fields; optional composite columns can therefore allow multiple rows that differ only by `NULL` in a nullable column. Prefer `NOT NULL` on composite members when you need strict “at most one row per pair” semantics. Other databases can differ; consult your backend
|
|
158
|
+
**Null semantics (SQLite):** With the default local SQLite engine, `UNIQUE` treats `NULL` as distinct from other `NULL` values for multi-column constraints unless columns are `NOT NULL`. Ferro maps nullability from your types and defaults like other fields; optional composite columns can therefore allow multiple rows that differ only by `NULL` in a nullable column. Prefer `NOT NULL` on composite members when you need strict “at most one row per pair” semantics. Other databases can differ; consult your backend's documentation when you target PostgreSQL or another backend with different unique/null behavior.
|
|
159
159
|
|
|
160
160
|
**Wire format:** Declarations use nested tuples in Python; the schema JSON sent to the Rust engine uses nested lists (`ferro_composite_uniques`) because JSON has no tuple type.
|
|
161
161
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
!!! warning "Feature Not Implemented"
|
|
4
4
|
**Multi-database support is not currently available in Ferro.** This documentation describes planned features. See [Coming Soon](../coming-soon.md#multiple-database-support) for more information.
|
|
5
5
|
|
|
6
|
-
Ferro currently supports only a single database connection per application. The examples below show the planned API.
|
|
6
|
+
Ferro currently supports only a single active database connection per application process. The examples below show the planned API, not something you can call today.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
Test your Ferro applications with pytest and test database isolation strategies.
|
|
4
4
|
|
|
5
|
+
## Ferro Test Matrix
|
|
6
|
+
|
|
7
|
+
The repository test suite supports two database modes:
|
|
8
|
+
|
|
9
|
+
- **Default SQLite run** for the full fast suite
|
|
10
|
+
- **Dual-backend matrix** for ORM coverage on both SQLite and PostgreSQL/Supabase
|
|
11
|
+
|
|
12
|
+
The matrix is opt-in so day-to-day test runs stay quick and deterministic.
|
|
13
|
+
|
|
14
|
+
### Local Setup
|
|
15
|
+
|
|
16
|
+
Install the development dependencies used by the matrix:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv sync --group dev
|
|
20
|
+
uv run maturin develop
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Set `FERRO_SUPABASE_URL` to a PostgreSQL connection string. A root `.env` file works well for local development:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
FERRO_SUPABASE_URL='postgresql://...'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The Postgres matrix reads `FERRO_SUPABASE_URL` from either the environment or the project `.env` file. Tests create a dedicated schema per test and use that schema as the search path so one shared Supabase database can still run isolated tests safely.
|
|
30
|
+
|
|
31
|
+
### Run The Default Suite
|
|
32
|
+
|
|
33
|
+
Run the normal SQLite-first suite:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv run pytest -q
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Run The Dual-Backend ORM Matrix
|
|
40
|
+
|
|
41
|
+
Run the backend-matrix and Postgres-specific tests on both SQLite and PostgreSQL:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv run pytest -m "backend_matrix or postgres_only" --db-backends=sqlite,postgres -q
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If you only want the PostgreSQL side of the matrix:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv run pytest -m "backend_matrix or postgres_only" --db-backends=postgres -q
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Test Markers
|
|
54
|
+
|
|
55
|
+
The repository uses three database markers:
|
|
56
|
+
|
|
57
|
+
- `backend_matrix`: run this test once per selected backend
|
|
58
|
+
- `sqlite_only`: keep SQLite-specific catalog, file-path, or pragma assertions on SQLite
|
|
59
|
+
- `postgres_only`: run Postgres/Supabase-specific assertions only when `FERRO_SUPABASE_URL` is configured
|
|
60
|
+
|
|
61
|
+
If `FERRO_SUPABASE_URL` is not set, `postgres_only` tests are skipped and `backend_matrix` tests run only on SQLite.
|
|
62
|
+
|
|
5
63
|
## Basic Setup
|
|
6
64
|
|
|
7
65
|
```python
|
|
@@ -9,25 +67,24 @@ Test your Ferro applications with pytest and test database isolation strategies.
|
|
|
9
67
|
import pytest
|
|
10
68
|
import ferro
|
|
11
69
|
|
|
12
|
-
@pytest.fixture
|
|
70
|
+
@pytest.fixture
|
|
13
71
|
async def db():
|
|
14
|
-
"""Connect to test database
|
|
72
|
+
"""Connect to a fresh test database for one test."""
|
|
15
73
|
await ferro.connect("sqlite::memory:", auto_migrate=True)
|
|
16
74
|
yield
|
|
17
|
-
|
|
75
|
+
ferro.reset_engine()
|
|
18
76
|
|
|
19
77
|
@pytest.fixture
|
|
20
78
|
async def db_transaction(db):
|
|
21
|
-
"""Wrap each test in
|
|
22
|
-
from ferro import
|
|
79
|
+
"""Wrap each test in Ferro's transaction() helper."""
|
|
80
|
+
from ferro import transaction
|
|
23
81
|
|
|
24
|
-
|
|
25
|
-
try:
|
|
82
|
+
async with transaction():
|
|
26
83
|
yield
|
|
27
|
-
finally:
|
|
28
|
-
await rollback_transaction(tx_id)
|
|
29
84
|
```
|
|
30
85
|
|
|
86
|
+
For backend-matrix tests, Ferro's own suite uses `--db-backends=sqlite,postgres` together with `backend_matrix` / `postgres_only` markers and a `FERRO_SUPABASE_URL` environment variable.
|
|
87
|
+
|
|
31
88
|
## Test Example
|
|
32
89
|
|
|
33
90
|
```python
|