ferro-orm 0.3.1__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.1 → ferro_orm-0.3.3}/.github/PERMISSIONS.md +34 -62
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/workflows/ci.yml +56 -1
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/workflows/publish.yml +4 -0
- ferro_orm-0.3.3/.github/workflows/release.yml +374 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/CHANGELOG.md +88 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/Cargo.lock +162 -15
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/Cargo.toml +2 -2
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/PKG-INFO +6 -1
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/README.md +2 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/api/utilities.md +5 -10
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/concepts/performance.md +3 -6
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/faq.md +4 -7
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/getting-started/installation.md +4 -8
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/database.md +51 -30
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/models-and-fields.md +1 -1
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/howto/multiple-databases.md +1 -1
- {ferro_orm-0.3.1 → 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.1 → ferro_orm-0.3.3}/pyproject.toml +12 -10
- ferro_orm-0.3.3/src/backend.rs +108 -0
- ferro_orm-0.3.3/src/connection.rs +128 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/_core.pyi +4 -2
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/_shadow_fk_types.py +30 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/metaclass.py +2 -48
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/migrations/alembic.py +2 -45
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/models.py +29 -31
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/query/nodes.py +14 -7
- {ferro_orm-0.3.1 → 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.1 → ferro_orm-0.3.3}/src/lib.rs +1 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/operations.rs +733 -210
- ferro_orm-0.3.3/src/query.rs +282 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/schema.rs +141 -34
- {ferro_orm-0.3.1 → 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.1 → ferro_orm-0.3.3}/tests/test_aggregation.py +1 -10
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_alembic_bridge.py +23 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_alembic_nullability.py +1 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_auto_migrate.py +8 -6
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_bulk_update.py +1 -10
- {ferro_orm-0.3.1 → 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.1 → ferro_orm-0.3.3}/tests/test_constraints.py +33 -14
- {ferro_orm-0.3.1 → 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.1 → ferro_orm-0.3.3}/tests/test_deletion.py +1 -10
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_documentation_features.py +3 -15
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_field_wrapper.py +15 -10
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_helpers.py +1 -10
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_hydration.py +1 -11
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_metadata.py +1 -9
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_one_to_one.py +60 -20
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_query_builder.py +1 -10
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_refresh.py +1 -10
- {ferro_orm-0.3.1 → 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.1 → ferro_orm-0.3.3}/tests/test_shadow_fk_types.py +10 -8
- {ferro_orm-0.3.1 → 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.1 → 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.1 → ferro_orm-0.3.3}/uv.lock +60 -7
- ferro_orm-0.3.1/.github/workflows/release.yml +0 -552
- ferro_orm-0.3.1/src/connection.rs +0 -68
- ferro_orm-0.3.1/src/query.rs +0 -112
- ferro_orm-0.3.1/tests/conftest.py +0 -48
- ferro_orm-0.3.1/tests/test_connection.py +0 -27
- ferro_orm-0.3.1/tests/test_schema_constraints.py +0 -69
- ferro_orm-0.3.1/tests/test_structural_types.py +0 -106
- ferro_orm-0.3.1/tests/test_transactions.py +0 -93
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.github/workflows/publish-docs.yml +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.gitignore +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/.python-version +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/LICENSE +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/TEST_RESULTS.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/api/fields.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/api/model.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/api/query.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/api/relationships.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/api/transactions.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/changelog.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/coming-soon.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/concepts/architecture.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/concepts/identity-map.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/concepts/type-safety.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/contributing.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/getting-started/next-steps.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/getting-started/tutorial.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/migrations.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/mutations.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/queries.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/relationships.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/guide/transactions.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/howto/pagination.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/howto/soft-deletes.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/howto/timestamps.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/index.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/migration-sqlalchemy.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/stylesheets/extra.css +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/docs/why-ferro.md +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/justfile +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/mkdocs.yml +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/scripts/demo_queries.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/__init__.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/_annotation_utils.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/base.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/composite_uniques.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/fields.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/py.typed +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/query/__init__.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/query/builder.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/relations/descriptors.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/src/ferro/state.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_docs_examples.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_metaclass_internals.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_models.py +0 -0
- {ferro_orm-0.3.1 → ferro_orm-0.3.3}/tests/test_relationship_engine.py +0 -0
|
@@ -45,37 +45,7 @@ All workflows use explicit, fine-grained permissions (principle of least privile
|
|
|
45
45
|
|
|
46
46
|
**Trigger:** Manual workflow dispatch
|
|
47
47
|
|
|
48
|
-
**
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
ci-gate packaging-smoke
|
|
52
|
-
\ /
|
|
53
|
-
prepare-release (computes bump, writes release.patch; no pushes)
|
|
54
|
-
/ | | \
|
|
55
|
-
build-wheels build-sdist test-wheels verify-docs
|
|
56
|
-
\ | /
|
|
57
|
-
promote-pypi (PyPI Trusted Publishing; first irreversible step)
|
|
58
|
-
|
|
|
59
|
-
promote-git (commit bump, push main, tag, create GH Release + assets)
|
|
60
|
-
|
|
|
61
|
-
promote-docs (deploys MkDocs site pinned to the new tag)
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Nothing is pushed, tagged, or published until every validation job above
|
|
65
|
-
`promote-pypi` has succeeded. PyPI is published first so that if anything
|
|
66
|
-
fails, `main` is never advanced to an "orphan" version.
|
|
67
|
-
|
|
68
|
-
**Permissions (prepare-release):**
|
|
69
|
-
```yaml
|
|
70
|
-
permissions:
|
|
71
|
-
contents: read
|
|
72
|
-
issues: read
|
|
73
|
-
pull-requests: read
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Prepare-release only needs read access because it just computes the bump and uploads a patch artifact — it does not push anything.
|
|
77
|
-
|
|
78
|
-
**Permissions (promote-git):**
|
|
48
|
+
**Permissions:**
|
|
79
49
|
```yaml
|
|
80
50
|
permissions:
|
|
81
51
|
contents: write
|
|
@@ -83,23 +53,33 @@ permissions:
|
|
|
83
53
|
pull-requests: write
|
|
84
54
|
```
|
|
85
55
|
|
|
86
|
-
|
|
87
|
-
|
|
56
|
+
**Why These Permissions:**
|
|
57
|
+
- `contents: write` - Allows the workflow to:
|
|
58
|
+
- Commit version bumps to pyproject.toml and Cargo.toml
|
|
88
59
|
- Push commits to the `main` branch
|
|
89
|
-
- Create and push git tags (e.g., `v0.
|
|
90
|
-
- Create GitHub
|
|
60
|
+
- Create and push git tags (e.g., `v0.2.0`)
|
|
61
|
+
- Create GitHub releases
|
|
91
62
|
|
|
92
|
-
- `issues: write`
|
|
93
|
-
|
|
63
|
+
- `issues: write` - Allows the workflow to:
|
|
64
|
+
- Update issue references in release notes
|
|
65
|
+
- Close issues automatically via commit messages
|
|
66
|
+
- Add labels or comments to issues
|
|
94
67
|
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
68
|
+
- `pull-requests: write` - Allows the workflow to:
|
|
69
|
+
- Update PR references in release notes
|
|
70
|
+
- Close PRs automatically via commit messages
|
|
71
|
+
- Add labels or comments to PRs
|
|
72
|
+
|
|
73
|
+
**What It Does:**
|
|
74
|
+
- **Preflight** builds all release wheels at the workflow SHA (same matrix as post-release wheel builds), in parallel with the CI gate and packaging smoke jobs. If any platform fails, the Create Release job does not run.
|
|
75
|
+
- Analyzes conventional commits
|
|
76
|
+
- Determines next version
|
|
77
|
+
- Updates version in both Python and Rust files
|
|
78
|
+
- Finalizes CHANGELOG.md
|
|
79
|
+
- Creates git tag
|
|
80
|
+
- Creates GitHub release
|
|
81
|
+
- Triggers publish workflow
|
|
82
|
+
- Reports whether release commits include `CHANGELOG.md`
|
|
103
83
|
|
|
104
84
|
**Required Secrets:**
|
|
105
85
|
- `RELEASE_DEPLOY_KEY` (private SSH key for a write-enabled deploy key)
|
|
@@ -109,15 +89,15 @@ permissions:
|
|
|
109
89
|
|
|
110
90
|
### 2. Build & Publish (jobs in `release.yml`)
|
|
111
91
|
|
|
112
|
-
**Trigger:** Part of `release.yml`
|
|
92
|
+
**Trigger:** Part of `release.yml` after the Create Release job (no reusable workflow). Wheel preflight runs in parallel with CI and packaging smoke, before that job.
|
|
113
93
|
|
|
114
94
|
Build, test, and publish jobs are defined directly in `release.yml` so PyPI Trusted Publishing receives a token with `workflow_ref: release.yml` (reusable workflows are not supported by PyPI).
|
|
115
95
|
|
|
116
96
|
**Permissions:**
|
|
117
97
|
|
|
118
|
-
**For build-wheels, build-sdist, test-wheels
|
|
98
|
+
**For build-wheels, build-sdist, test-wheels:** (default - read-only)
|
|
119
99
|
|
|
120
|
-
**For
|
|
100
|
+
**For publish-pypi job:**
|
|
121
101
|
```yaml
|
|
122
102
|
permissions:
|
|
123
103
|
id-token: write
|
|
@@ -127,10 +107,10 @@ permissions:
|
|
|
127
107
|
- `id-token: write` - Allows the job to request an OIDC token and authenticate with PyPI using Trusted Publishing.
|
|
128
108
|
|
|
129
109
|
**What It Does:**
|
|
130
|
-
- Builds wheels for multiple platforms
|
|
131
|
-
- Builds source distribution
|
|
132
|
-
-
|
|
133
|
-
-
|
|
110
|
+
- Builds wheels for multiple platforms from the release tag
|
|
111
|
+
- Builds source distribution
|
|
112
|
+
- Tests built packages
|
|
113
|
+
- Publishes to PyPI using OIDC authentication
|
|
134
114
|
|
|
135
115
|
---
|
|
136
116
|
|
|
@@ -269,7 +249,7 @@ Permission to manage GitHub Pages deployments:
|
|
|
269
249
|
|
|
270
250
|
### Release Completed But Publish/Docs Did Not Run
|
|
271
251
|
|
|
272
|
-
**Cause:** `release.yml` gates downstream jobs. If CI, packaging smoke, or release creation fails, publish/docs are skipped.
|
|
252
|
+
**Cause:** `release.yml` gates downstream jobs. If CI, packaging smoke, preflight wheel builds, or release creation fails, publish/docs are skipped.
|
|
273
253
|
|
|
274
254
|
**Solution:**
|
|
275
255
|
- Open the failed `release.yml` run and inspect the first failed job.
|
|
@@ -289,18 +269,10 @@ Permission to manage GitHub Pages deployments:
|
|
|
289
269
|
**Cause:** Missing `id-token: write` permission
|
|
290
270
|
|
|
291
271
|
**Solution:**
|
|
292
|
-
- Ensure `
|
|
272
|
+
- Ensure `publish-pypi` job has `id-token: write`
|
|
293
273
|
- Verify PyPI trusted publisher is configured correctly
|
|
294
274
|
- Check environment name matches (`pypi`)
|
|
295
275
|
|
|
296
|
-
### Release says "0.3.0 has already been released!" for a version that was already shipped
|
|
297
|
-
|
|
298
|
-
**Cause:** `actions/checkout@v4` runs `git fetch --no-tags` by default, even with `fetch-depth: 0`. Without tags, `semantic-release` picks an older baseline tag, computes a minor/patch bump, and lands on the same version number as a prior release.
|
|
299
|
-
|
|
300
|
-
**Solution:**
|
|
301
|
-
- The `prepare-release` job sets `fetch-tags: true` on its checkout step and then runs a `git describe` vs `semantic-release --print-last-released-tag` comparison as a guard. Both must be present.
|
|
302
|
-
- If this check fails in a run, confirm no one has removed `fetch-tags: true` from the checkout step.
|
|
303
|
-
|
|
304
276
|
---
|
|
305
277
|
|
|
306
278
|
## Verification
|
|
@@ -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!"
|
|
@@ -60,6 +60,10 @@ jobs:
|
|
|
60
60
|
|
|
61
61
|
- name: Build wheels
|
|
62
62
|
uses: PyO3/maturin-action@v1.48.0
|
|
63
|
+
env:
|
|
64
|
+
# ring (via rustls/sqlx): manylinux2014-cross gcc does not define __ARM_ARCH for .S asm.
|
|
65
|
+
# https://github.com/briansmith/ring/issues/1728
|
|
66
|
+
CFLAGS_aarch64_unknown_linux_gnu: ${{ matrix.platform.name == 'linux-aarch64' && '-D__ARM_ARCH=8' || '' }}
|
|
63
67
|
with:
|
|
64
68
|
target: ${{ matrix.platform.target }}
|
|
65
69
|
args: --release --out dist --find-interpreter
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
prerelease:
|
|
7
|
+
description: 'Create a pre-release'
|
|
8
|
+
required: false
|
|
9
|
+
type: boolean
|
|
10
|
+
default: false
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: release-${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
# Same maturin targets as build-wheels, but against the current ref *before*
|
|
18
|
+
# semantic-release mutates the repo. Runs in parallel with CI gate and
|
|
19
|
+
# packaging smoke so the wall-clock pipeline stays shorter.
|
|
20
|
+
preflight-build-wheels:
|
|
21
|
+
name: Preflight build wheels (${{ matrix.platform.name }})
|
|
22
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
platform:
|
|
27
|
+
- name: linux-x86_64
|
|
28
|
+
runner: ubuntu-latest
|
|
29
|
+
target: x86_64
|
|
30
|
+
manylinux: auto
|
|
31
|
+
- name: linux-aarch64
|
|
32
|
+
runner: ubuntu-latest
|
|
33
|
+
target: aarch64
|
|
34
|
+
manylinux: auto
|
|
35
|
+
- name: macos-aarch64
|
|
36
|
+
runner: macos-latest
|
|
37
|
+
target: aarch64
|
|
38
|
+
manylinux: ''
|
|
39
|
+
- name: windows-x64
|
|
40
|
+
runner: windows-latest
|
|
41
|
+
target: x64
|
|
42
|
+
manylinux: ''
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- name: Checkout repository
|
|
46
|
+
uses: actions/checkout@v4
|
|
47
|
+
with:
|
|
48
|
+
ref: ${{ github.sha }}
|
|
49
|
+
|
|
50
|
+
- name: Set up Python
|
|
51
|
+
uses: actions/setup-python@v5
|
|
52
|
+
with:
|
|
53
|
+
python-version: '3.13'
|
|
54
|
+
|
|
55
|
+
- name: Build wheels (preflight)
|
|
56
|
+
uses: PyO3/maturin-action@v1.48.0
|
|
57
|
+
env:
|
|
58
|
+
# ring (via rustls/sqlx): manylinux2014-cross gcc does not define __ARM_ARCH for .S asm.
|
|
59
|
+
# https://github.com/briansmith/ring/issues/1728
|
|
60
|
+
CFLAGS_aarch64_unknown_linux_gnu: ${{ matrix.platform.name == 'linux-aarch64' && '-D__ARM_ARCH=8' || '' }}
|
|
61
|
+
with:
|
|
62
|
+
target: ${{ matrix.platform.target }}
|
|
63
|
+
args: --release --out dist --find-interpreter
|
|
64
|
+
manylinux: ${{ matrix.platform.manylinux }}
|
|
65
|
+
sccache: 'true'
|
|
66
|
+
|
|
67
|
+
ci-gate:
|
|
68
|
+
name: CI Gate
|
|
69
|
+
uses: ./.github/workflows/ci.yml
|
|
70
|
+
|
|
71
|
+
packaging-smoke:
|
|
72
|
+
name: Packaging Smoke Gate
|
|
73
|
+
uses: ./.github/workflows/packaging-smoke.yml
|
|
74
|
+
|
|
75
|
+
release:
|
|
76
|
+
name: Create Release
|
|
77
|
+
needs: [ci-gate, packaging-smoke, preflight-build-wheels]
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
outputs:
|
|
80
|
+
release_created: ${{ steps.verify_release.outputs.release_created }}
|
|
81
|
+
release_ref: ${{ steps.release_ref.outputs.release_ref }}
|
|
82
|
+
permissions:
|
|
83
|
+
contents: write
|
|
84
|
+
issues: write
|
|
85
|
+
pull-requests: write
|
|
86
|
+
|
|
87
|
+
steps:
|
|
88
|
+
- name: Checkout repository
|
|
89
|
+
uses: actions/checkout@v4
|
|
90
|
+
with:
|
|
91
|
+
fetch-depth: 0
|
|
92
|
+
ssh-key: ${{ secrets.RELEASE_DEPLOY_KEY }}
|
|
93
|
+
persist-credentials: false
|
|
94
|
+
|
|
95
|
+
- name: Set up Python
|
|
96
|
+
uses: actions/setup-python@v5
|
|
97
|
+
with:
|
|
98
|
+
python-version: '3.13'
|
|
99
|
+
|
|
100
|
+
- name: Install UV
|
|
101
|
+
uses: astral-sh/setup-uv@v5
|
|
102
|
+
with:
|
|
103
|
+
enable-cache: true
|
|
104
|
+
|
|
105
|
+
- name: Set up Rust
|
|
106
|
+
uses: dtolnay/rust-toolchain@stable
|
|
107
|
+
|
|
108
|
+
- name: Cache Rust dependencies
|
|
109
|
+
uses: Swatinem/rust-cache@v2
|
|
110
|
+
with:
|
|
111
|
+
prefix-key: "v1-rust"
|
|
112
|
+
|
|
113
|
+
- name: Install dependencies
|
|
114
|
+
run: |
|
|
115
|
+
uv sync --only-group release --no-install-project --python 3.13
|
|
116
|
+
|
|
117
|
+
- name: Configure Git
|
|
118
|
+
run: |
|
|
119
|
+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
120
|
+
git config --local user.name "github-actions[bot]"
|
|
121
|
+
git remote set-url origin "git@github.com:${{ github.repository }}.git"
|
|
122
|
+
|
|
123
|
+
- name: Run semantic-release
|
|
124
|
+
env:
|
|
125
|
+
# Prefer a PAT so release events can trigger downstream workflows.
|
|
126
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
|
127
|
+
run: |
|
|
128
|
+
# Use the --prerelease flag if requested via manual trigger
|
|
129
|
+
PRERELEASE_FLAG=""
|
|
130
|
+
if [ "${{ github.event.inputs.prerelease }}" == "true" ]; then
|
|
131
|
+
PRERELEASE_FLAG="--prerelease"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# This command will:
|
|
135
|
+
# 1. Bump version in pyproject.toml and Cargo.toml
|
|
136
|
+
# 2. Update CHANGELOG.md
|
|
137
|
+
# 3. Create a commit and push it
|
|
138
|
+
# 4. Create a tag and push it
|
|
139
|
+
# 5. Create a GitHub Release (publishing is handled by the wheels/publish workflow)
|
|
140
|
+
uv run semantic-release version $PRERELEASE_FLAG
|
|
141
|
+
|
|
142
|
+
- name: Verify release commit updates changelog
|
|
143
|
+
id: verify_release
|
|
144
|
+
shell: bash
|
|
145
|
+
run: |
|
|
146
|
+
LAST_SUBJECT="$(git log -1 --pretty=%s)"
|
|
147
|
+
if [[ "$LAST_SUBJECT" != chore\(release\):* ]]; then
|
|
148
|
+
echo "No release commit created in this run; skipping changelog validation."
|
|
149
|
+
echo "release_created=false" >> "$GITHUB_OUTPUT"
|
|
150
|
+
echo "changelog_validated=skipped" >> "$GITHUB_OUTPUT"
|
|
151
|
+
echo "release_subject=$LAST_SUBJECT" >> "$GITHUB_OUTPUT"
|
|
152
|
+
exit 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo "release_created=true" >> "$GITHUB_OUTPUT"
|
|
156
|
+
echo "release_subject=$LAST_SUBJECT" >> "$GITHUB_OUTPUT"
|
|
157
|
+
|
|
158
|
+
CHANGED_FILES="$(git show --name-only --pretty='' HEAD)"
|
|
159
|
+
if [[ "$CHANGED_FILES" != *"CHANGELOG.md"* ]]; then
|
|
160
|
+
echo "WARNING: release commit does not include CHANGELOG.md update."
|
|
161
|
+
echo "Changed files:"
|
|
162
|
+
echo "$CHANGED_FILES"
|
|
163
|
+
echo "changelog_validated=missing" >> "$GITHUB_OUTPUT"
|
|
164
|
+
exit 0
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
echo "changelog_validated=passed" >> "$GITHUB_OUTPUT"
|
|
168
|
+
echo "Validated: release commit includes CHANGELOG.md."
|
|
169
|
+
|
|
170
|
+
- name: Capture released ref
|
|
171
|
+
id: release_ref
|
|
172
|
+
shell: bash
|
|
173
|
+
run: |
|
|
174
|
+
if [[ "${{ steps.verify_release.outputs.release_created }}" != "true" ]]; then
|
|
175
|
+
echo "release_ref=" >> "$GITHUB_OUTPUT"
|
|
176
|
+
exit 0
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
RELEASE_TAG="$(git tag --points-at HEAD | head -n1)"
|
|
180
|
+
if [[ -z "$RELEASE_TAG" ]]; then
|
|
181
|
+
echo "ERROR: expected release tag on HEAD but none was found."
|
|
182
|
+
exit 1
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
echo "release_ref=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
|
|
186
|
+
echo "Using release ref: $RELEASE_TAG"
|
|
187
|
+
|
|
188
|
+
- name: Release summary
|
|
189
|
+
if: always()
|
|
190
|
+
shell: bash
|
|
191
|
+
run: |
|
|
192
|
+
{
|
|
193
|
+
echo "## Release workflow summary"
|
|
194
|
+
echo ""
|
|
195
|
+
echo "- Trigger: \`${{ github.event_name }}\`"
|
|
196
|
+
echo "- Ref: \`${{ github.ref_name }}\`"
|
|
197
|
+
echo "- Release commit created: \`${{ steps.verify_release.outputs.release_created || 'unknown' }}\`"
|
|
198
|
+
echo "- Changelog validation: \`${{ steps.verify_release.outputs.changelog_validated || 'unknown' }}\`"
|
|
199
|
+
echo "- Release ref: \`${{ steps.release_ref.outputs.release_ref || 'n/a' }}\`"
|
|
200
|
+
echo "- Last commit subject: \`${{ steps.verify_release.outputs.release_subject || 'n/a' }}\`"
|
|
201
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
202
|
+
|
|
203
|
+
# Build and publish jobs are inlined here (not in a reusable workflow) so that
|
|
204
|
+
# PyPI Trusted Publishing sees workflow_ref = release.yml. Reusable workflows
|
|
205
|
+
# are not supported by PyPI Trusted Publishing.
|
|
206
|
+
build-wheels:
|
|
207
|
+
name: Build wheels (${{ matrix.platform.name }})
|
|
208
|
+
needs: [release]
|
|
209
|
+
if: needs.release.outputs.release_created == 'true'
|
|
210
|
+
runs-on: ${{ matrix.platform.runner }}
|
|
211
|
+
strategy:
|
|
212
|
+
fail-fast: false
|
|
213
|
+
matrix:
|
|
214
|
+
platform:
|
|
215
|
+
- name: linux-x86_64
|
|
216
|
+
runner: ubuntu-latest
|
|
217
|
+
target: x86_64
|
|
218
|
+
manylinux: auto
|
|
219
|
+
- name: linux-aarch64
|
|
220
|
+
runner: ubuntu-latest
|
|
221
|
+
target: aarch64
|
|
222
|
+
manylinux: auto
|
|
223
|
+
- name: macos-aarch64
|
|
224
|
+
runner: macos-latest
|
|
225
|
+
target: aarch64
|
|
226
|
+
manylinux: ''
|
|
227
|
+
- name: windows-x64
|
|
228
|
+
runner: windows-latest
|
|
229
|
+
target: x64
|
|
230
|
+
manylinux: ''
|
|
231
|
+
|
|
232
|
+
steps:
|
|
233
|
+
- name: Checkout repository
|
|
234
|
+
uses: actions/checkout@v4
|
|
235
|
+
with:
|
|
236
|
+
ref: ${{ needs.release.outputs.release_ref }}
|
|
237
|
+
|
|
238
|
+
- name: Set up Python
|
|
239
|
+
uses: actions/setup-python@v5
|
|
240
|
+
with:
|
|
241
|
+
python-version: '3.13'
|
|
242
|
+
|
|
243
|
+
- name: Build wheels
|
|
244
|
+
uses: PyO3/maturin-action@v1.48.0
|
|
245
|
+
env:
|
|
246
|
+
# ring (via rustls/sqlx): manylinux2014-cross gcc does not define __ARM_ARCH for .S asm.
|
|
247
|
+
# https://github.com/briansmith/ring/issues/1728
|
|
248
|
+
CFLAGS_aarch64_unknown_linux_gnu: ${{ matrix.platform.name == 'linux-aarch64' && '-D__ARM_ARCH=8' || '' }}
|
|
249
|
+
with:
|
|
250
|
+
target: ${{ matrix.platform.target }}
|
|
251
|
+
args: --release --out dist --find-interpreter
|
|
252
|
+
manylinux: ${{ matrix.platform.manylinux }}
|
|
253
|
+
sccache: 'true'
|
|
254
|
+
|
|
255
|
+
- name: Upload wheels
|
|
256
|
+
uses: actions/upload-artifact@v4
|
|
257
|
+
with:
|
|
258
|
+
name: wheels-${{ matrix.platform.name }}
|
|
259
|
+
path: dist/*.whl
|
|
260
|
+
|
|
261
|
+
build-sdist:
|
|
262
|
+
name: Build sdist
|
|
263
|
+
needs: [release]
|
|
264
|
+
if: needs.release.outputs.release_created == 'true'
|
|
265
|
+
runs-on: ubuntu-latest
|
|
266
|
+
steps:
|
|
267
|
+
- name: Checkout repository
|
|
268
|
+
uses: actions/checkout@v4
|
|
269
|
+
with:
|
|
270
|
+
ref: ${{ needs.release.outputs.release_ref }}
|
|
271
|
+
|
|
272
|
+
- name: Build sdist
|
|
273
|
+
uses: PyO3/maturin-action@v1.48.0
|
|
274
|
+
with:
|
|
275
|
+
command: sdist
|
|
276
|
+
args: --out dist
|
|
277
|
+
|
|
278
|
+
- name: Upload sdist
|
|
279
|
+
uses: actions/upload-artifact@v4
|
|
280
|
+
with:
|
|
281
|
+
name: sdist
|
|
282
|
+
path: dist/*.tar.gz
|
|
283
|
+
|
|
284
|
+
test-wheels:
|
|
285
|
+
name: Test wheels (${{ matrix.os }})
|
|
286
|
+
needs: [release, build-wheels]
|
|
287
|
+
if: needs.release.outputs.release_created == 'true'
|
|
288
|
+
runs-on: ${{ matrix.os }}
|
|
289
|
+
strategy:
|
|
290
|
+
fail-fast: false
|
|
291
|
+
matrix:
|
|
292
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
293
|
+
steps:
|
|
294
|
+
- name: Checkout repository
|
|
295
|
+
uses: actions/checkout@v4
|
|
296
|
+
with:
|
|
297
|
+
ref: ${{ needs.release.outputs.release_ref }}
|
|
298
|
+
|
|
299
|
+
- name: Set up Python
|
|
300
|
+
uses: actions/setup-python@v5
|
|
301
|
+
with:
|
|
302
|
+
python-version: '3.13'
|
|
303
|
+
|
|
304
|
+
- name: Download wheels
|
|
305
|
+
uses: actions/download-artifact@v4
|
|
306
|
+
with:
|
|
307
|
+
path: dist
|
|
308
|
+
pattern: wheels-*
|
|
309
|
+
merge-multiple: true
|
|
310
|
+
|
|
311
|
+
- name: Install wheel
|
|
312
|
+
shell: bash
|
|
313
|
+
run: |
|
|
314
|
+
python -m pip install --upgrade pip
|
|
315
|
+
python -m pip install --find-links dist ferro-orm
|
|
316
|
+
|
|
317
|
+
- name: Test import
|
|
318
|
+
shell: bash
|
|
319
|
+
run: |
|
|
320
|
+
python -c "import ferro; print('Ferro imported successfully')"
|
|
321
|
+
|
|
322
|
+
- name: Run basic smoke test
|
|
323
|
+
shell: bash
|
|
324
|
+
run: |
|
|
325
|
+
python -c "
|
|
326
|
+
import asyncio
|
|
327
|
+
from ferro import Model, FerroField, connect
|
|
328
|
+
from typing import Annotated
|
|
329
|
+
|
|
330
|
+
class TestModel(Model):
|
|
331
|
+
id: Annotated[int, FerroField(primary_key=True)]
|
|
332
|
+
name: str
|
|
333
|
+
|
|
334
|
+
async def test():
|
|
335
|
+
await connect('sqlite::memory:')
|
|
336
|
+
print('Connection test passed')
|
|
337
|
+
|
|
338
|
+
asyncio.run(test())
|
|
339
|
+
"
|
|
340
|
+
|
|
341
|
+
publish-pypi:
|
|
342
|
+
name: Publish to PyPI
|
|
343
|
+
needs: [release, build-wheels, build-sdist, test-wheels]
|
|
344
|
+
if: needs.release.outputs.release_created == 'true'
|
|
345
|
+
runs-on: ubuntu-latest
|
|
346
|
+
environment:
|
|
347
|
+
name: pypi
|
|
348
|
+
url: https://pypi.org/p/ferro-orm
|
|
349
|
+
permissions:
|
|
350
|
+
id-token: write
|
|
351
|
+
steps:
|
|
352
|
+
- name: Download all artifacts
|
|
353
|
+
uses: actions/download-artifact@v4
|
|
354
|
+
with:
|
|
355
|
+
path: dist
|
|
356
|
+
merge-multiple: true
|
|
357
|
+
|
|
358
|
+
- name: Publish to PyPI
|
|
359
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
360
|
+
with:
|
|
361
|
+
skip-existing: true
|
|
362
|
+
verbose: true
|
|
363
|
+
|
|
364
|
+
deploy-docs:
|
|
365
|
+
name: Deploy Documentation
|
|
366
|
+
needs: [release, publish-pypi]
|
|
367
|
+
if: needs.release.outputs.release_created == 'true'
|
|
368
|
+
uses: ./.github/workflows/publish-docs.yml
|
|
369
|
+
permissions:
|
|
370
|
+
contents: read
|
|
371
|
+
pages: write
|
|
372
|
+
id-token: write
|
|
373
|
+
with:
|
|
374
|
+
ref: ${{ needs.release.outputs.release_ref }}
|