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.
- ferro_orm-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
- ferro_orm-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +37 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/publish-docs.yml +2 -1
- ferro_orm-0.3.0/.github/workflows/release.yml +491 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.gitignore +4 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/CHANGELOG.md +102 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/Cargo.lock +330 -229
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/Cargo.toml +1 -1
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/PKG-INFO +1 -1
- ferro_orm-0.3.0/docs/TEST_RESULTS.md +208 -0
- ferro_orm-0.3.0/docs/api/fields.md +21 -0
- ferro_orm-0.3.0/docs/api/model.md +29 -0
- ferro_orm-0.3.0/docs/api/query.md +24 -0
- ferro_orm-0.3.0/docs/api/relationships.md +28 -0
- ferro_orm-0.3.0/docs/api/transactions.md +36 -0
- ferro_orm-0.3.0/docs/api/utilities.md +75 -0
- ferro_orm-0.3.0/docs/changelog.md +36 -0
- ferro_orm-0.3.0/docs/coming-soon.md +514 -0
- ferro_orm-0.3.0/docs/concepts/architecture.md +330 -0
- ferro_orm-0.3.0/docs/concepts/identity-map.md +285 -0
- ferro_orm-0.3.0/docs/concepts/performance.md +221 -0
- ferro_orm-0.3.0/docs/concepts/type-safety.md +159 -0
- ferro_orm-0.3.0/docs/faq.md +255 -0
- ferro_orm-0.3.0/docs/getting-started/installation.md +90 -0
- ferro_orm-0.3.0/docs/getting-started/next-steps.md +159 -0
- ferro_orm-0.3.0/docs/getting-started/tutorial.md +388 -0
- ferro_orm-0.3.0/docs/guide/database.md +266 -0
- ferro_orm-0.3.0/docs/guide/migrations.md +415 -0
- ferro_orm-0.3.0/docs/guide/models-and-fields.md +218 -0
- ferro_orm-0.3.0/docs/guide/mutations.md +461 -0
- ferro_orm-0.3.0/docs/guide/queries.md +329 -0
- ferro_orm-0.3.0/docs/guide/relationships.md +341 -0
- ferro_orm-0.3.0/docs/guide/transactions.md +357 -0
- ferro_orm-0.3.0/docs/howto/multiple-databases.md +74 -0
- ferro_orm-0.3.0/docs/howto/pagination.md +353 -0
- ferro_orm-0.3.0/docs/howto/soft-deletes.md +86 -0
- ferro_orm-0.3.0/docs/howto/testing.md +123 -0
- ferro_orm-0.3.0/docs/howto/timestamps.md +57 -0
- ferro_orm-0.3.0/docs/index.md +142 -0
- ferro_orm-0.3.0/docs/migration-sqlalchemy.md +150 -0
- ferro_orm-0.3.0/docs/stylesheets/extra.css +27 -0
- ferro_orm-0.3.0/docs/why-ferro.md +206 -0
- ferro_orm-0.3.0/mkdocs.yml +161 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/pyproject.toml +4 -3
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/scripts/demo_queries.py +3 -3
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/__init__.py +2 -2
- ferro_orm-0.3.0/src/ferro/_shadow_fk_types.py +137 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/base.py +5 -2
- ferro_orm-0.3.0/src/ferro/composite_uniques.py +97 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/fields.py +37 -25
- ferro_orm-0.3.0/src/ferro/metaclass.py +402 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/migrations/alembic.py +23 -1
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/models.py +19 -0
- ferro_orm-0.3.0/src/ferro/query/__init__.py +6 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/query/builder.py +2 -2
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/relations/__init__.py +29 -15
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/relations/descriptors.py +22 -24
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/schema.rs +51 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/conftest.py +4 -3
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_alembic_bridge.py +26 -4
- ferro_orm-0.3.0/tests/test_auto_migrate.py +78 -0
- ferro_orm-0.3.0/tests/test_composite_unique.py +324 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_crud.py +25 -25
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_docs_examples.py +3 -0
- ferro_orm-0.3.0/tests/test_documentation_features.py +830 -0
- ferro_orm-0.3.0/tests/test_metaclass_internals.py +359 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_one_to_one.py +2 -2
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_relationship_engine.py +78 -11
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_schema_constraints.py +8 -6
- ferro_orm-0.3.0/tests/test_shadow_fk_types.py +258 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/uv.lock +1 -1
- ferro_orm-0.2.0/.github/workflows/release.yml +0 -320
- ferro_orm-0.2.0/docs/api/core-models.md +0 -6
- ferro_orm-0.2.0/docs/api/field-metadata.md +0 -17
- ferro_orm-0.2.0/docs/api/global-functions.md +0 -21
- ferro_orm-0.2.0/docs/api/query-builder.md +0 -11
- ferro_orm-0.2.0/docs/api.md +0 -10
- ferro_orm-0.2.0/docs/connection.md +0 -48
- ferro_orm-0.2.0/docs/fields.md +0 -80
- ferro_orm-0.2.0/docs/index.md +0 -3
- ferro_orm-0.2.0/docs/migrations.md +0 -52
- ferro_orm-0.2.0/docs/models.md +0 -64
- ferro_orm-0.2.0/docs/queries.md +0 -89
- ferro_orm-0.2.0/docs/relations.md +0 -85
- ferro_orm-0.2.0/docs/transactions.md +0 -57
- ferro_orm-0.2.0/mkdocs.yml +0 -104
- ferro_orm-0.2.0/src/ferro/metaclass.py +0 -216
- ferro_orm-0.2.0/src/ferro/query/__init__.py +0 -6
- ferro_orm-0.2.0/tests/test_auto_migrate.py +0 -37
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/PERMISSIONS.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/PYPI_CHECKLIST.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/PYPI_SETUP.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/generated/wheels.generated.yml +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/pull_request_template.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/ci.yml +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/packaging-smoke.yml +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.github/workflows/publish.yml +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.pre-commit-config.yaml +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/.python-version +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/CONTRIBUTING.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/LICENSE +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/README.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/docs/contributing.md +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/justfile +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/connection.rs +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/_core.pyi +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/migrations/__init__.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/py.typed +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/query/nodes.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/ferro/state.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/lib.rs +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/operations.rs +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/query.rs +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/src/state.rs +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_aggregation.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_alembic_autogenerate.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_alembic_type_mapping.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_bulk_update.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_connection.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_constraints.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_deletion.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_field_wrapper.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_helpers.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_hydration.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_metadata.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_models.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_query_builder.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_refresh.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_schema.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_string_search.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_structural_types.py +0 -0
- {ferro_orm-0.2.0 → ferro_orm-0.3.0}/tests/test_temporal_types.py +0 -0
- {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
|
|
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 }}
|
|
@@ -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
|