ferro-orm 0.2.0__tar.gz → 0.3.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.
Files changed (133) hide show
  1. ferro_orm-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
  2. ferro_orm-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +37 -0
  3. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/publish-docs.yml +2 -1
  4. ferro_orm-0.3.0/.github/workflows/release.yml +491 -0
  5. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.gitignore +4 -0
  6. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/CHANGELOG.md +102 -0
  7. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/Cargo.lock +330 -229
  8. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/Cargo.toml +1 -1
  9. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/PKG-INFO +1 -1
  10. ferro_orm-0.3.0/docs/TEST_RESULTS.md +208 -0
  11. ferro_orm-0.3.0/docs/api/fields.md +21 -0
  12. ferro_orm-0.3.0/docs/api/model.md +29 -0
  13. ferro_orm-0.3.0/docs/api/query.md +24 -0
  14. ferro_orm-0.3.0/docs/api/relationships.md +28 -0
  15. ferro_orm-0.3.0/docs/api/transactions.md +36 -0
  16. ferro_orm-0.3.0/docs/api/utilities.md +75 -0
  17. ferro_orm-0.3.0/docs/changelog.md +36 -0
  18. ferro_orm-0.3.0/docs/coming-soon.md +514 -0
  19. ferro_orm-0.3.0/docs/concepts/architecture.md +330 -0
  20. ferro_orm-0.3.0/docs/concepts/identity-map.md +285 -0
  21. ferro_orm-0.3.0/docs/concepts/performance.md +221 -0
  22. ferro_orm-0.3.0/docs/concepts/type-safety.md +159 -0
  23. ferro_orm-0.3.0/docs/faq.md +255 -0
  24. ferro_orm-0.3.0/docs/getting-started/installation.md +90 -0
  25. ferro_orm-0.3.0/docs/getting-started/next-steps.md +159 -0
  26. ferro_orm-0.3.0/docs/getting-started/tutorial.md +388 -0
  27. ferro_orm-0.3.0/docs/guide/database.md +266 -0
  28. ferro_orm-0.3.0/docs/guide/migrations.md +415 -0
  29. ferro_orm-0.3.0/docs/guide/models-and-fields.md +218 -0
  30. ferro_orm-0.3.0/docs/guide/mutations.md +461 -0
  31. ferro_orm-0.3.0/docs/guide/queries.md +329 -0
  32. ferro_orm-0.3.0/docs/guide/relationships.md +341 -0
  33. ferro_orm-0.3.0/docs/guide/transactions.md +357 -0
  34. ferro_orm-0.3.0/docs/howto/multiple-databases.md +74 -0
  35. ferro_orm-0.3.0/docs/howto/pagination.md +353 -0
  36. ferro_orm-0.3.0/docs/howto/soft-deletes.md +86 -0
  37. ferro_orm-0.3.0/docs/howto/testing.md +123 -0
  38. ferro_orm-0.3.0/docs/howto/timestamps.md +57 -0
  39. ferro_orm-0.3.0/docs/index.md +142 -0
  40. ferro_orm-0.3.0/docs/migration-sqlalchemy.md +150 -0
  41. ferro_orm-0.3.0/docs/stylesheets/extra.css +27 -0
  42. ferro_orm-0.3.0/docs/why-ferro.md +206 -0
  43. ferro_orm-0.3.0/mkdocs.yml +161 -0
  44. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/pyproject.toml +4 -3
  45. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/scripts/demo_queries.py +3 -3
  46. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/__init__.py +2 -2
  47. ferro_orm-0.3.0/src/ferro/_shadow_fk_types.py +137 -0
  48. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/base.py +5 -2
  49. ferro_orm-0.3.0/src/ferro/composite_uniques.py +97 -0
  50. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/fields.py +37 -25
  51. ferro_orm-0.3.0/src/ferro/metaclass.py +402 -0
  52. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/migrations/alembic.py +23 -1
  53. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/models.py +19 -0
  54. ferro_orm-0.3.0/src/ferro/query/__init__.py +6 -0
  55. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/query/builder.py +2 -2
  56. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/relations/__init__.py +29 -15
  57. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/relations/descriptors.py +22 -24
  58. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/schema.rs +51 -0
  59. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/conftest.py +4 -3
  60. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_alembic_bridge.py +26 -4
  61. ferro_orm-0.3.0/tests/test_auto_migrate.py +78 -0
  62. ferro_orm-0.3.0/tests/test_composite_unique.py +324 -0
  63. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_crud.py +25 -25
  64. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_docs_examples.py +3 -0
  65. ferro_orm-0.3.0/tests/test_documentation_features.py +830 -0
  66. ferro_orm-0.3.0/tests/test_metaclass_internals.py +359 -0
  67. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_one_to_one.py +2 -2
  68. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_relationship_engine.py +78 -11
  69. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_schema_constraints.py +8 -6
  70. ferro_orm-0.3.0/tests/test_shadow_fk_types.py +258 -0
  71. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/uv.lock +1 -1
  72. ferro_orm-0.2.0/.github/workflows/release.yml +0 -320
  73. ferro_orm-0.2.0/docs/api/core-models.md +0 -6
  74. ferro_orm-0.2.0/docs/api/field-metadata.md +0 -17
  75. ferro_orm-0.2.0/docs/api/global-functions.md +0 -21
  76. ferro_orm-0.2.0/docs/api/query-builder.md +0 -11
  77. ferro_orm-0.2.0/docs/api.md +0 -10
  78. ferro_orm-0.2.0/docs/connection.md +0 -48
  79. ferro_orm-0.2.0/docs/fields.md +0 -80
  80. ferro_orm-0.2.0/docs/index.md +0 -3
  81. ferro_orm-0.2.0/docs/migrations.md +0 -52
  82. ferro_orm-0.2.0/docs/models.md +0 -64
  83. ferro_orm-0.2.0/docs/queries.md +0 -89
  84. ferro_orm-0.2.0/docs/relations.md +0 -85
  85. ferro_orm-0.2.0/docs/transactions.md +0 -57
  86. ferro_orm-0.2.0/mkdocs.yml +0 -104
  87. ferro_orm-0.2.0/src/ferro/metaclass.py +0 -216
  88. ferro_orm-0.2.0/src/ferro/query/__init__.py +0 -6
  89. ferro_orm-0.2.0/tests/test_auto_migrate.py +0 -37
  90. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/PERMISSIONS.md +0 -0
  91. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/PYPI_CHECKLIST.md +0 -0
  92. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/PYPI_SETUP.md +0 -0
  93. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/generated/wheels.generated.yml +0 -0
  94. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/pull_request_template.md +0 -0
  95. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/ci.yml +0 -0
  96. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/packaging-smoke.yml +0 -0
  97. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/publish.yml +0 -0
  98. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.pre-commit-config.yaml +0 -0
  99. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.python-version +0 -0
  100. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/CONTRIBUTING.md +0 -0
  101. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/LICENSE +0 -0
  102. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/README.md +0 -0
  103. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/docs/contributing.md +0 -0
  104. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/justfile +0 -0
  105. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/connection.rs +0 -0
  106. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/_core.pyi +0 -0
  107. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/migrations/__init__.py +0 -0
  108. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/py.typed +0 -0
  109. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/query/nodes.py +0 -0
  110. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/state.py +0 -0
  111. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/lib.rs +0 -0
  112. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/operations.rs +0 -0
  113. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/query.rs +0 -0
  114. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/state.rs +0 -0
  115. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_aggregation.py +0 -0
  116. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_alembic_autogenerate.py +0 -0
  117. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_alembic_type_mapping.py +0 -0
  118. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_bulk_update.py +0 -0
  119. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_connection.py +0 -0
  120. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_constraints.py +0 -0
  121. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_deletion.py +0 -0
  122. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_field_wrapper.py +0 -0
  123. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_helpers.py +0 -0
  124. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_hydration.py +0 -0
  125. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_metadata.py +0 -0
  126. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_models.py +0 -0
  127. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_query_builder.py +0 -0
  128. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_refresh.py +0 -0
  129. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_schema.py +0 -0
  130. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_string_search.py +0 -0
  131. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_structural_types.py +0 -0
  132. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_temporal_types.py +0 -0
  133. {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_transactions.py +0 -0
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a bug or unexpected behavior
4
+ title: "`bug` "
5
+ labels: bug
6
+ assignees: Ox54
7
+
8
+ ---
9
+
10
+ ## Summary
11
+
12
+ <!-- Brief description of the bug. -->
13
+
14
+ ## Environment
15
+
16
+ - **Ferro version:** <!-- e.g. 0.1.0 or git commit -->
17
+ - **Python version:**
18
+ - **OS:** <!-- e.g. macOS 14, Ubuntu 22.04 -->
19
+ - **Database:** <!-- e.g. SQLite 3.43, PostgreSQL 15 -->
20
+
21
+ ## Steps to reproduce
22
+
23
+ 1.
24
+ 2.
25
+ 3.
26
+
27
+ ## Expected behavior
28
+
29
+ <!-- What should happen. -->
30
+
31
+ ## Actual behavior
32
+
33
+ <!-- What actually happens. Include error messages or tracebacks if relevant. -->
34
+
35
+ ## Minimal example (optional)
36
+
37
+ ```python
38
+ # Minimal code that reproduces the issue
39
+ ```
40
+
41
+ ## Additional context
42
+
43
+ <!-- Logs, screenshots, or other details that might help. -->
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest a new feature or enhancement for Ferro
4
+ title: "`feat` "
5
+ labels: feature
6
+ assignees: Ox54
7
+
8
+ ---
9
+
10
+ ## Summary
11
+
12
+ <!-- One or two sentences describing the feature and why it would be useful. -->
13
+
14
+ ## Current state
15
+
16
+ **Status:** <!-- e.g. Not implemented / Partially implemented -->
17
+
18
+ <!-- If partially implemented, list what works and what does not. -->
19
+
20
+ **Documentation references:**
21
+ - <!-- e.g. docs/coming-soon.md, docs/guide/queries.md (lines X–Y) -->
22
+
23
+ **Current workaround:** <!-- How can users achieve this today, if at all? -->
24
+
25
+ ## Proposed API (examples)
26
+
27
+ ```python
28
+ # Example of how the feature would be used
29
+ ```
30
+
31
+ <!-- Describe how this would integrate with the existing API (query builder, models, etc.). -->
32
+
33
+ ## Acceptance criteria
34
+
35
+ - [ ]
36
+ - [ ]
37
+ - [ ] Docs updated (e.g. remove from Coming Soon, add to relevant guide).
@@ -1,6 +1,7 @@
1
1
  name: Publish Docs
2
2
 
3
3
  on:
4
+ workflow_dispatch:
4
5
  workflow_call:
5
6
  inputs:
6
7
  ref:
@@ -41,7 +42,7 @@ jobs:
41
42
 
42
43
  - name: Build MkDocs site
43
44
  run: |
44
- uv run mkdocs build --strict
45
+ uv run --no-sync mkdocs build
45
46
 
46
47
  - name: Upload Pages artifact
47
48
  uses: actions/upload-pages-artifact@v3
@@ -0,0 +1,491 @@
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
+ ci-gate:
18
+ name: CI Gate
19
+ uses: ./.github/workflows/ci.yml
20
+
21
+ packaging-smoke:
22
+ name: Packaging Smoke Gate
23
+ uses: ./.github/workflows/packaging-smoke.yml
24
+
25
+ # Bump versions and changelog locally only; export a patch so every later job
26
+ # validates the exact tree that would be released. Nothing is pushed until
27
+ # publish-github-release after wheels, sdist, tests, and docs all pass.
28
+ prepare-release:
29
+ name: Prepare release (no push)
30
+ needs: [ci-gate, packaging-smoke]
31
+ runs-on: ubuntu-latest
32
+ outputs:
33
+ release_created: ${{ steps.verify_release.outputs.release_created }}
34
+ release_base_sha: ${{ steps.base_sha.outputs.release_base_sha }}
35
+ release_tag: ${{ steps.verify_release.outputs.release_tag }}
36
+ release_version: ${{ steps.verify_release.outputs.release_version }}
37
+ permissions:
38
+ contents: read
39
+ issues: read
40
+ pull-requests: read
41
+
42
+ steps:
43
+ - name: Record workflow base SHA
44
+ id: base_sha
45
+ run: echo "release_base_sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
46
+
47
+ - name: Checkout repository
48
+ uses: actions/checkout@v4
49
+ with:
50
+ fetch-depth: 0
51
+
52
+ - name: Set up Python
53
+ uses: actions/setup-python@v5
54
+ with:
55
+ python-version: '3.13'
56
+
57
+ - name: Install UV
58
+ uses: astral-sh/setup-uv@v5
59
+ with:
60
+ enable-cache: true
61
+
62
+ - name: Install dependencies
63
+ run: |
64
+ uv sync --only-group release --no-install-project --python 3.13
65
+
66
+ - name: Run semantic-release (local bump only)
67
+ env:
68
+ GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
69
+ run: |
70
+ PRERELEASE_FLAG=""
71
+ if [ "${{ github.event.inputs.prerelease }}" == "true" ]; then
72
+ PRERELEASE_FLAG="--prerelease"
73
+ fi
74
+
75
+ # Writes version files + CHANGELOG only; no commit, push, tag, or VCS
76
+ # release. --skip-build defers packaging to validation jobs.
77
+ uv run semantic-release version $PRERELEASE_FLAG \
78
+ --no-vcs-release --no-tag --no-commit --no-push --skip-build
79
+
80
+ - name: Verify release candidate and write patch
81
+ id: verify_release
82
+ shell: bash
83
+ run: |
84
+ set -euo pipefail
85
+ # Normalize to a single index-vs-HEAD patch (stable for git apply on runners).
86
+ git add -A
87
+ git diff --cached HEAD > release.patch
88
+
89
+ if [[ ! -s release.patch ]]; then
90
+ echo "No semantic version bump in this run (index matches HEAD)."
91
+ echo "release_created=false" >> "$GITHUB_OUTPUT"
92
+ echo "changelog_validated=skipped" >> "$GITHUB_OUTPUT"
93
+ echo "release_tag=" >> "$GITHUB_OUTPUT"
94
+ echo "release_version=" >> "$GITHUB_OUTPUT"
95
+ exit 0
96
+ fi
97
+
98
+ if ! grep -qE '^diff --git a/CHANGELOG\.md b/CHANGELOG\.md' release.patch; then
99
+ echo "WARNING: expected CHANGELOG.md to change for a release bump."
100
+ echo "release_created=false" >> "$GITHUB_OUTPUT"
101
+ echo "changelog_validated=missing" >> "$GITHUB_OUTPUT"
102
+ echo "release_tag=" >> "$GITHUB_OUTPUT"
103
+ echo "release_version=" >> "$GITHUB_OUTPUT"
104
+ exit 0
105
+ fi
106
+
107
+ VERSION="$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")"
108
+ echo "release_created=true" >> "$GITHUB_OUTPUT"
109
+ echo "changelog_validated=passed" >> "$GITHUB_OUTPUT"
110
+ echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
111
+ echo "release_version=${VERSION}" >> "$GITHUB_OUTPUT"
112
+ echo "Prepared v${VERSION} as patch against ${{ github.sha }}"
113
+
114
+ - name: Upload release patch
115
+ if: steps.verify_release.outputs.release_created == 'true'
116
+ uses: actions/upload-artifact@v4
117
+ with:
118
+ name: release-patch
119
+ path: release.patch
120
+
121
+ - name: Release summary
122
+ if: always()
123
+ shell: bash
124
+ run: |
125
+ {
126
+ echo "## Release workflow summary"
127
+ echo ""
128
+ echo "- Trigger: \`${{ github.event_name }}\`"
129
+ echo "- Base ref: \`${{ github.ref_name }}\` @ \`${{ github.sha }}\`"
130
+ echo "- Release candidate prepared: \`${{ steps.verify_release.outputs.release_created || 'unknown' }}\`"
131
+ echo "- Changelog validation: \`${{ steps.verify_release.outputs.changelog_validated || 'unknown' }}\`"
132
+ echo "- Tag if published: \`${{ steps.verify_release.outputs.release_tag || 'n/a' }}\`"
133
+ } >> "$GITHUB_STEP_SUMMARY"
134
+
135
+ # Build and publish jobs are inlined here (not in a reusable workflow) so that
136
+ # PyPI Trusted Publishing sees workflow_ref = release.yml. Reusable workflows
137
+ # are not supported by PyPI Trusted Publishing.
138
+ build-wheels:
139
+ name: Build wheels (${{ matrix.platform.name }})
140
+ needs: [prepare-release]
141
+ if: needs.prepare-release.outputs.release_created == 'true'
142
+ runs-on: ${{ matrix.platform.runner }}
143
+ strategy:
144
+ fail-fast: false
145
+ matrix:
146
+ platform:
147
+ - name: linux-x86_64
148
+ runner: ubuntu-latest
149
+ target: x86_64
150
+ manylinux: auto
151
+ - name: linux-aarch64
152
+ runner: ubuntu-latest
153
+ target: aarch64
154
+ manylinux: auto
155
+ - name: macos-aarch64
156
+ runner: macos-latest
157
+ target: aarch64
158
+ manylinux: ''
159
+ - name: windows-x64
160
+ runner: windows-latest
161
+ target: x64
162
+ manylinux: ''
163
+
164
+ steps:
165
+ - name: Disable Git CRLF conversion before checkout
166
+ shell: bash
167
+ run: git config --global core.autocrlf false
168
+ - name: Checkout repository
169
+ uses: actions/checkout@v4
170
+ with:
171
+ ref: ${{ needs.prepare-release.outputs.release_base_sha }}
172
+
173
+ - name: Download release patch
174
+ uses: actions/download-artifact@v4
175
+ with:
176
+ name: release-patch
177
+ path: release-patch
178
+
179
+ - name: Apply release patch
180
+ shell: bash
181
+ run: |
182
+ git config core.autocrlf false
183
+ git apply release-patch/release.patch
184
+ rm -rf release-patch
185
+
186
+ - name: Set up Python
187
+ uses: actions/setup-python@v5
188
+ with:
189
+ python-version: '3.13'
190
+
191
+ - name: Build wheels
192
+ uses: PyO3/maturin-action@v1.48.0
193
+ with:
194
+ target: ${{ matrix.platform.target }}
195
+ args: --release --out dist --find-interpreter
196
+ manylinux: ${{ matrix.platform.manylinux }}
197
+ sccache: 'true'
198
+
199
+ - name: Upload wheels
200
+ uses: actions/upload-artifact@v4
201
+ with:
202
+ name: wheels-${{ matrix.platform.name }}
203
+ path: dist/*.whl
204
+
205
+ build-sdist:
206
+ name: Build sdist
207
+ needs: [prepare-release]
208
+ if: needs.prepare-release.outputs.release_created == 'true'
209
+ runs-on: ubuntu-latest
210
+ steps:
211
+ - name: Disable Git CRLF conversion before checkout
212
+ shell: bash
213
+ run: git config --global core.autocrlf false
214
+ - name: Checkout repository
215
+ uses: actions/checkout@v4
216
+ with:
217
+ ref: ${{ needs.prepare-release.outputs.release_base_sha }}
218
+
219
+ - name: Download release patch
220
+ uses: actions/download-artifact@v4
221
+ with:
222
+ name: release-patch
223
+ path: release-patch
224
+
225
+ - name: Apply release patch
226
+ shell: bash
227
+ run: |
228
+ git config core.autocrlf false
229
+ git apply release-patch/release.patch
230
+ rm -rf release-patch
231
+
232
+ - name: Build sdist
233
+ uses: PyO3/maturin-action@v1.48.0
234
+ with:
235
+ command: sdist
236
+ args: --out dist
237
+
238
+ - name: Upload sdist
239
+ uses: actions/upload-artifact@v4
240
+ with:
241
+ name: sdist
242
+ path: dist/*.tar.gz
243
+
244
+ test-wheels:
245
+ name: Test wheels (${{ matrix.os }})
246
+ needs: [prepare-release, build-wheels]
247
+ if: needs.prepare-release.outputs.release_created == 'true'
248
+ runs-on: ${{ matrix.os }}
249
+ strategy:
250
+ fail-fast: false
251
+ matrix:
252
+ os: [ubuntu-latest, windows-latest, macos-latest]
253
+ steps:
254
+ - name: Disable Git CRLF conversion before checkout
255
+ shell: bash
256
+ run: git config --global core.autocrlf false
257
+ - name: Checkout repository
258
+ uses: actions/checkout@v4
259
+ with:
260
+ ref: ${{ needs.prepare-release.outputs.release_base_sha }}
261
+
262
+ - name: Download release patch
263
+ uses: actions/download-artifact@v4
264
+ with:
265
+ name: release-patch
266
+ path: release-patch
267
+
268
+ - name: Apply release patch
269
+ shell: bash
270
+ run: |
271
+ git config core.autocrlf false
272
+ git apply release-patch/release.patch
273
+ rm -rf release-patch
274
+
275
+ - name: Set up Python
276
+ uses: actions/setup-python@v5
277
+ with:
278
+ python-version: '3.13'
279
+
280
+ - name: Download wheels
281
+ uses: actions/download-artifact@v4
282
+ with:
283
+ path: dist
284
+ pattern: wheels-*
285
+ merge-multiple: true
286
+
287
+ - name: Install wheel
288
+ shell: bash
289
+ run: |
290
+ python -m pip install --upgrade pip
291
+ python -m pip install --find-links dist ferro-orm
292
+
293
+ - name: Test import
294
+ shell: bash
295
+ run: |
296
+ python -c "import ferro; print('Ferro imported successfully')"
297
+
298
+ - name: Run basic smoke test
299
+ shell: bash
300
+ run: |
301
+ python -c "
302
+ import asyncio
303
+ from ferro import Model, FerroField, connect
304
+ from typing import Annotated
305
+
306
+ class TestModel(Model):
307
+ id: Annotated[int, FerroField(primary_key=True)]
308
+ name: str
309
+
310
+ async def test():
311
+ await connect('sqlite::memory:')
312
+ print('Connection test passed')
313
+
314
+ asyncio.run(test())
315
+ "
316
+
317
+ verify-docs:
318
+ name: Verify documentation build
319
+ needs: [prepare-release]
320
+ if: needs.prepare-release.outputs.release_created == 'true'
321
+ runs-on: ubuntu-latest
322
+ steps:
323
+ - name: Disable Git CRLF conversion before checkout
324
+ shell: bash
325
+ run: git config --global core.autocrlf false
326
+ - name: Checkout repository
327
+ uses: actions/checkout@v4
328
+ with:
329
+ ref: ${{ needs.prepare-release.outputs.release_base_sha }}
330
+
331
+ - name: Download release patch
332
+ uses: actions/download-artifact@v4
333
+ with:
334
+ name: release-patch
335
+ path: release-patch
336
+
337
+ - name: Apply release patch
338
+ shell: bash
339
+ run: |
340
+ git config core.autocrlf false
341
+ git apply release-patch/release.patch
342
+ rm -rf release-patch
343
+
344
+ - name: Set up Python
345
+ uses: actions/setup-python@v5
346
+ with:
347
+ python-version: '3.13'
348
+
349
+ - name: Install UV
350
+ uses: astral-sh/setup-uv@v5
351
+ with:
352
+ enable-cache: true
353
+
354
+ - name: Install documentation dependencies
355
+ run: |
356
+ uv sync --only-group docs --no-install-project --python 3.13
357
+
358
+ - name: Build MkDocs site
359
+ run: |
360
+ uv run --no-sync mkdocs build
361
+
362
+ publish-github-release:
363
+ name: Commit, tag, and create GitHub Release
364
+ needs: [prepare-release, build-wheels, build-sdist, test-wheels, verify-docs]
365
+ if: needs.prepare-release.outputs.release_created == 'true'
366
+ runs-on: ubuntu-latest
367
+ permissions:
368
+ contents: write
369
+ issues: write
370
+ pull-requests: write
371
+ steps:
372
+ - name: Disable Git CRLF conversion before checkout
373
+ shell: bash
374
+ run: git config --global core.autocrlf false
375
+ - name: Checkout repository
376
+ uses: actions/checkout@v4
377
+ with:
378
+ ref: ${{ needs.prepare-release.outputs.release_base_sha }}
379
+ fetch-depth: 0
380
+ ssh-key: ${{ secrets.RELEASE_DEPLOY_KEY }}
381
+ # Required so the deploy key stays loaded for git push in the next step.
382
+ persist-credentials: true
383
+
384
+ - name: Download release patch
385
+ uses: actions/download-artifact@v4
386
+ with:
387
+ name: release-patch
388
+ path: release-patch
389
+
390
+ - name: Apply release patch and push release commit
391
+ env:
392
+ VERSION: ${{ needs.prepare-release.outputs.release_version }}
393
+ TAG: ${{ needs.prepare-release.outputs.release_tag }}
394
+ run: |
395
+ set -euo pipefail
396
+ git config core.autocrlf false
397
+ git apply release-patch/release.patch
398
+ rm -rf release-patch
399
+
400
+ git config user.email "github-actions[bot]@users.noreply.github.com"
401
+ git config user.name "github-actions[bot]"
402
+ git remote set-url origin "git@github.com:${{ github.repository }}.git"
403
+
404
+ if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then
405
+ echo "ERROR: tag ${TAG} already exists on the remote."
406
+ exit 1
407
+ fi
408
+
409
+ git add -A
410
+ git commit --author="semantic-release <semantic-release@github.com>" \
411
+ -m "chore(release): ${VERSION}" \
412
+ -m "Automatically generated by python-semantic-release"
413
+ git push origin "HEAD:refs/heads/${{ github.ref_name }}"
414
+
415
+ git tag "${TAG}"
416
+ git push origin "refs/tags/${TAG}"
417
+
418
+ # semantic-release matches branch config (e.g. branches.main); detached HEAD fails with
419
+ # "Detached HEAD state cannot match any release groups".
420
+ - name: Attach HEAD to branch for semantic-release
421
+ run: git switch -C "${{ github.ref_name }}"
422
+
423
+ - name: Set up Python
424
+ uses: actions/setup-python@v5
425
+ with:
426
+ python-version: '3.13'
427
+
428
+ - name: Install UV
429
+ uses: astral-sh/setup-uv@v5
430
+ with:
431
+ enable-cache: true
432
+
433
+ - name: Set up Rust
434
+ uses: dtolnay/rust-toolchain@stable
435
+
436
+ - name: Cache Rust dependencies
437
+ uses: Swatinem/rust-cache@v2
438
+ with:
439
+ prefix-key: "v1-rust"
440
+
441
+ - name: Install dependencies
442
+ run: |
443
+ uv sync --only-group release --no-install-project --python 3.13
444
+
445
+ - name: Publish GitHub release (assets + VCS release)
446
+ env:
447
+ GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
448
+ run: |
449
+ uv run semantic-release publish --tag "${{ needs.prepare-release.outputs.release_tag }}"
450
+
451
+ publish-pypi:
452
+ name: Publish to PyPI
453
+ needs: [prepare-release, publish-github-release]
454
+ if: needs.prepare-release.outputs.release_created == 'true'
455
+ runs-on: ubuntu-latest
456
+ environment:
457
+ name: pypi
458
+ url: https://pypi.org/p/ferro-orm
459
+ permissions:
460
+ id-token: write
461
+ steps:
462
+ - name: Download sdist
463
+ uses: actions/download-artifact@v4
464
+ with:
465
+ name: sdist
466
+ path: dist
467
+
468
+ - name: Download wheels
469
+ uses: actions/download-artifact@v4
470
+ with:
471
+ pattern: wheels-*
472
+ path: dist
473
+ merge-multiple: true
474
+
475
+ - name: Publish to PyPI
476
+ uses: pypa/gh-action-pypi-publish@release/v1
477
+ with:
478
+ skip-existing: true
479
+ verbose: true
480
+
481
+ deploy-docs:
482
+ name: Deploy Documentation
483
+ needs: [prepare-release, publish-pypi]
484
+ if: needs.prepare-release.outputs.release_created == 'true'
485
+ uses: ./.github/workflows/publish-docs.yml
486
+ permissions:
487
+ contents: read
488
+ pages: write
489
+ id-token: write
490
+ with:
491
+ ref: ${{ needs.prepare-release.outputs.release_tag }}
@@ -241,3 +241,7 @@ ferro_test.db
241
241
  .cursor
242
242
  playground.ipynb
243
243
  IMPLEMENTATION.md
244
+ Cargo.lock
245
+ demo.db
246
+ docs/superpowers/plans/2026-04-22-shadow-fk-types.md
247
+ docs/superpowers/specs/2026-04-22-shadow-fk-types-design.md
@@ -1,6 +1,108 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.3.0 (2026-04-22)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Align composite unique index names and harden Alembic/Rust handling
9
+ ([`3350481`](https://github.com/syn54x/ferro-orm/commit/33504812d37d93bf69c2be8f6bee6f390803a460))
10
+
11
+ - Refresh Pydantic FieldInfo when reconciling shadow FK types
12
+ ([`6cf1ac8`](https://github.com/syn54x/ferro-orm/commit/6cf1ac8c2e361df8de11795a8151c34d17a39445))
13
+
14
+ ### Chores
15
+
16
+ - Remove doc
17
+ ([`16e4028`](https://github.com/syn54x/ferro-orm/commit/16e4028f72fc47109b2511d1feb23811c831f32c))
18
+
19
+ ### Continuous Integration
20
+
21
+ - Fix release
22
+ ([`688d01b`](https://github.com/syn54x/ferro-orm/commit/688d01bdae0aff1f82e4a1bb60dd1b8ab35e1d01))
23
+
24
+ - Fix release
25
+ ([`249e460`](https://github.com/syn54x/ferro-orm/commit/249e46058bac87215920083a9d45557f3c58b62f))
26
+
27
+ - Fix release
28
+ ([`888e15e`](https://github.com/syn54x/ferro-orm/commit/888e15eff693d1e1bfa279d809e790e52cd7ce25))
29
+
30
+ - Fix release
31
+ ([`58bb5b2`](https://github.com/syn54x/ferro-orm/commit/58bb5b2b0962481b3cfbf3ffbe6c6a2653b213c0))
32
+
33
+ - Reorder release steps to prevent tagging before checks are complete
34
+ ([`ad1fd8d`](https://github.com/syn54x/ferro-orm/commit/ad1fd8d5ba08bdd7a1bcd257fff3fc12ff458c12))
35
+
36
+ ### Documentation
37
+
38
+ - Complete documentation restructure and implementation summary
39
+ ([`937e75e`](https://github.com/syn54x/ferro-orm/commit/937e75ee7b5c526aca776dd8409f9e0df5f0e892))
40
+
41
+ - Enhance shadow field documentation and clarify relationship resolution process
42
+ ([`1d350fd`](https://github.com/syn54x/ferro-orm/commit/1d350fd728310a5b9a24f129986f873a84a8592f))
43
+
44
+ ### Features
45
+
46
+ - Composite unique constraints and default M2M pair uniqueness
47
+ ([`dc12880`](https://github.com/syn54x/ferro-orm/commit/dc12880b7b8676c088183edf1f32b48a36314448))
48
+
49
+ - Derive shadow FK types from related PK and reconcile after resolve
50
+ ([`d3ae486`](https://github.com/syn54x/ferro-orm/commit/d3ae4862858ccd51f62d62a939e6a90b8efb8980))
51
+
52
+ ### Testing
53
+
54
+ - UUID FK save reparenting and bulk_create coverage
55
+ ([`6c93cea`](https://github.com/syn54x/ferro-orm/commit/6c93cea7906ac264b266342ecf71602c7aff6ed6))
56
+
57
+
58
+ ## v0.2.1 (2026-04-20)
59
+
60
+ ### Bug Fixes
61
+
62
+ - Defer annotations resolution
63
+ ([`edd39ab`](https://github.com/syn54x/ferro-orm/commit/edd39abdec7b34410040394d430fd30833e02aee))
64
+
65
+ ### Chores
66
+
67
+ - Update patch_tags in pyproject.toml to include refactor
68
+ ([`36c29a7`](https://github.com/syn54x/ferro-orm/commit/36c29a71f0f95845d50d1dd6fdbc14b2c4b20ac2))
69
+
70
+ ### Continuous Integration
71
+
72
+ - Fix release & mkdocs publish workflows
73
+ ([`630dc7c`](https://github.com/syn54x/ferro-orm/commit/630dc7cea32da02602acc037e1d8da722d3fb593))
74
+
75
+ ### Documentation
76
+
77
+ - Restructure documentation following Diátaxis framework
78
+ ([`b3c2cde`](https://github.com/syn54x/ferro-orm/commit/b3c2cde1d0bde589ad0f08a34002202bca81e5e5))
79
+
80
+ - Update BackRef references and enhance field documentation
81
+ ([`baf73ba`](https://github.com/syn54x/ferro-orm/commit/baf73ba03abd554ca8159bc718aa1785b08691ae))
82
+
83
+ - Update model field annotations to support optional back references
84
+ ([`2044896`](https://github.com/syn54x/ferro-orm/commit/20448966854d33e64c03a97831f640af279e93b4))
85
+
86
+ ### Refactoring
87
+
88
+ - Enhance model relationship descriptors and improve field handling
89
+ ([`6275ebb`](https://github.com/syn54x/ferro-orm/commit/6275ebb9be3ce7a419fb84c381c0a90eec22a5e9))
90
+
91
+ - Modularize metaclass __new__ method for easier testing and maintenance
92
+ ([`e514b95`](https://github.com/syn54x/ferro-orm/commit/e514b950cb94e333418f7bd556b8e5b48bf7298e))
93
+
94
+ - Rename BackRelationship to BackRef and add back_ref to Field
95
+ ([`d24d32d`](https://github.com/syn54x/ferro-orm/commit/d24d32d3402b51cb738ac2a2c8f396b98d4de632))
96
+
97
+ - Update demo_queries to use BackRef instead of BackRelationship
98
+ ([`51799ad`](https://github.com/syn54x/ferro-orm/commit/51799adc1a0b90f937cab7651cf8276f53a16100))
99
+
100
+ ### Testing
101
+
102
+ - Update references from BackRelationship to BackRef in test files
103
+ ([`60a1d87`](https://github.com/syn54x/ferro-orm/commit/60a1d87d88fe93996dc0b479c75d40aad3ff143b))
104
+
105
+
4
106
  ## v0.2.0 (2026-02-14)
5
107
 
6
108
  ### Chores