sqlalchemy-state-machine 1.11.0__tar.gz → 2.2.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.
- sqlalchemy_state_machine-2.2.0/.github/SECURITY.md +8 -0
- sqlalchemy_state_machine-2.2.0/.github/dependabot.yml +10 -0
- sqlalchemy_state_machine-2.2.0/.github/workflows/ci.yml +36 -0
- sqlalchemy_state_machine-2.2.0/.github/workflows/publish.yml +57 -0
- sqlalchemy_state_machine-2.2.0/.gitignore +10 -0
- sqlalchemy_state_machine-2.2.0/CHANGELOG.md +175 -0
- sqlalchemy_state_machine-2.2.0/Makefile +134 -0
- sqlalchemy_state_machine-2.2.0/PKG-INFO +160 -0
- sqlalchemy_state_machine-2.2.0/README.md +134 -0
- sqlalchemy_state_machine-2.2.0/codecov.yml +20 -0
- sqlalchemy_state_machine-2.2.0/examples/basic_usage.py +51 -0
- sqlalchemy_state_machine-2.2.0/pyproject.toml +45 -0
- sqlalchemy_state_machine-2.2.0/src/sqlalchemy_state_machine/__init__.py +16 -0
- sqlalchemy_state_machine-2.2.0/src/sqlalchemy_state_machine/exceptions.py +9 -0
- sqlalchemy_state_machine-2.2.0/src/sqlalchemy_state_machine/machine.py +93 -0
- sqlalchemy_state_machine-2.2.0/src/sqlalchemy_state_machine/mixin.py +90 -0
- sqlalchemy_state_machine-2.2.0/tests/test_examples.py +6 -0
- sqlalchemy_state_machine-2.2.0/tests/test_machine.py +62 -0
- sqlalchemy_state_machine-2.2.0/tests/test_mixin.py +216 -0
- sqlalchemy_state_machine-2.2.0/tests/test_public_api.py +13 -0
- sqlalchemy_state_machine-2.2.0/uv.lock +626 -0
- sqlalchemy-state-machine-1.11.0/MANIFEST.in +0 -6
- sqlalchemy-state-machine-1.11.0/PKG-INFO +0 -99
- sqlalchemy-state-machine-1.11.0/README.md +0 -69
- sqlalchemy-state-machine-1.11.0/pyproject.toml +0 -18
- sqlalchemy-state-machine-1.11.0/setup.cfg +0 -4
- sqlalchemy-state-machine-1.11.0/setup.py +0 -53
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine/__init__.py +0 -6
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine/state_mixin.py +0 -41
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine/version.py +0 -3
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine.egg-info/PKG-INFO +0 -99
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine.egg-info/SOURCES.txt +0 -14
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine.egg-info/dependency_links.txt +0 -1
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine.egg-info/not-zip-safe +0 -1
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine.egg-info/requires.txt +0 -2
- sqlalchemy-state-machine-1.11.0/sqlalchemy_state_machine.egg-info/top_level.txt +0 -2
- {sqlalchemy-state-machine-1.11.0 → sqlalchemy_state_machine-2.2.0}/LICENSE +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
quality:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.14"
|
|
17
|
+
- uses: astral-sh/setup-uv@v7
|
|
18
|
+
- run: uv sync --all-groups --locked
|
|
19
|
+
- run: uv run --locked ruff format --check src tests examples
|
|
20
|
+
- run: uv run --locked ruff check src tests examples
|
|
21
|
+
- run: uv run --locked mypy src
|
|
22
|
+
|
|
23
|
+
test:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: ${{ matrix.python-version }}
|
|
34
|
+
- uses: astral-sh/setup-uv@v7
|
|
35
|
+
- run: uv sync --all-groups --locked
|
|
36
|
+
- run: uv run --locked pytest --cov=sqlalchemy_state_machine --cov-report=term-missing
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*", "[0-9]*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with:
|
|
14
|
+
python-version: "3.14"
|
|
15
|
+
- uses: astral-sh/setup-uv@v7
|
|
16
|
+
- run: uv sync --all-groups --locked
|
|
17
|
+
- name: Verify tag matches project version
|
|
18
|
+
run: |
|
|
19
|
+
uv run --locked python - <<'PY'
|
|
20
|
+
import os
|
|
21
|
+
import tomllib
|
|
22
|
+
|
|
23
|
+
with open("pyproject.toml", "rb") as pyproject:
|
|
24
|
+
version = tomllib.load(pyproject)["project"]["version"]
|
|
25
|
+
|
|
26
|
+
tag = os.environ["GITHUB_REF_NAME"]
|
|
27
|
+
if tag.removeprefix("v") != version:
|
|
28
|
+
raise SystemExit(
|
|
29
|
+
f"Release tag {tag!r} does not match [project].version "
|
|
30
|
+
f"{version!r} in pyproject.toml."
|
|
31
|
+
)
|
|
32
|
+
PY
|
|
33
|
+
- run: uv run --locked ruff format --check src tests examples
|
|
34
|
+
- run: uv run --locked ruff check src tests examples
|
|
35
|
+
- run: uv run --locked mypy src
|
|
36
|
+
- run: uv run --locked pytest --cov=sqlalchemy_state_machine --cov-report=term-missing
|
|
37
|
+
- run: uv build
|
|
38
|
+
- run: uv run --locked python -m zipfile -t dist/*.whl
|
|
39
|
+
- uses: actions/upload-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: package-distributions
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
needs: build
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
environment:
|
|
48
|
+
name: pypi
|
|
49
|
+
url: https://pypi.org/p/sqlalchemy-state-machine
|
|
50
|
+
permissions:
|
|
51
|
+
id-token: write
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/download-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: package-distributions
|
|
56
|
+
path: dist/
|
|
57
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0 (2026-07-20)
|
|
4
|
+
|
|
5
|
+
* Update changelog [Pavel Liashkov]
|
|
6
|
+
* feat: modernize state machine library [Pavel Liashkov]
|
|
7
|
+
|
|
8
|
+
## 1.11.0 (2024-10-18)
|
|
9
|
+
|
|
10
|
+
* Update pipeline [Pavel Liashkov]
|
|
11
|
+
* Update version [Pavel Liashkov]
|
|
12
|
+
|
|
13
|
+
## 1.10.0 (2023-08-03)
|
|
14
|
+
|
|
15
|
+
* Update sqlalchemy requirement from <1.5,>=1.4 to >=1.4,<2.1 [dependabot[bot]]
|
|
16
|
+
* Update pipeline [Pavel Liashkov]
|
|
17
|
+
* Update version [Pavel Liashkov]
|
|
18
|
+
|
|
19
|
+
## 1.9.0 (2023-01-19)
|
|
20
|
+
|
|
21
|
+
* Update transitions requirement from <0.9,>=0.8 to >=0.8,<0.10 [dependabot[bot]]
|
|
22
|
+
* Bump version [Pavel Liashkov]
|
|
23
|
+
|
|
24
|
+
## 1.8.0 (2021-07-26)
|
|
25
|
+
|
|
26
|
+
* Relax reqs version [Pavel Liashkov]
|
|
27
|
+
|
|
28
|
+
## 1.7.0 (2021-07-23)
|
|
29
|
+
|
|
30
|
+
* Bump sqlalchemy from 1.4.18 to 1.4.19 [dependabot[bot]]
|
|
31
|
+
* Bump sqlalchemy from 1.4.19 to 1.4.22 [dependabot[bot]]
|
|
32
|
+
* Update version.py [Pavel Liashkov]
|
|
33
|
+
|
|
34
|
+
## 1.6.0 (2021-06-18)
|
|
35
|
+
|
|
36
|
+
* Bump sqlalchemy from 1.4.17 to 1.4.18 [dependabot[bot]]
|
|
37
|
+
* Update version.py [Pavel Liashkov]
|
|
38
|
+
|
|
39
|
+
## 1.5.0 (2021-06-04)
|
|
40
|
+
|
|
41
|
+
* Bump sqlalchemy from 1.4.15 to 1.4.17 [dependabot[bot]]
|
|
42
|
+
* Update version.py [Pavel Liashkov]
|
|
43
|
+
|
|
44
|
+
## 1.4.0 (2021-05-18)
|
|
45
|
+
|
|
46
|
+
* Bump sqlalchemy from 1.4.13 to 1.4.15 [dependabot[bot]]
|
|
47
|
+
* Update version.py [Pavel Liashkov]
|
|
48
|
+
|
|
49
|
+
## 1.3.0 (2021-05-05)
|
|
50
|
+
|
|
51
|
+
* Bump sqlalchemy from 1.4.8 to 1.4.13 [dependabot[bot]]
|
|
52
|
+
* Update version.py [Pavel Liashkov]
|
|
53
|
+
|
|
54
|
+
## 1.2.0 (2021-04-16)
|
|
55
|
+
|
|
56
|
+
* Bump transitions from 0.8.7 to 0.8.8 [dependabot[bot]]
|
|
57
|
+
* Bump sqlalchemy from 1.4.5 to 1.4.8 [dependabot[bot]]
|
|
58
|
+
* Update version.py [Pavel Liashkov]
|
|
59
|
+
|
|
60
|
+
## 1.1.0 (2021-04-05)
|
|
61
|
+
|
|
62
|
+
* Bump sqlalchemy from 1.4.0 to 1.4.2 [dependabot[bot]]
|
|
63
|
+
* Bump sqlalchemy from 1.4.2 to 1.4.5 [dependabot[bot]]
|
|
64
|
+
* Bump version [Pavel Liashkov]
|
|
65
|
+
|
|
66
|
+
## 1.0.0 (2021-03-17)
|
|
67
|
+
|
|
68
|
+
* Add command for check code quality [Pavel Liashkov]
|
|
69
|
+
* Fix githib workflow [Pavel Liashkov]
|
|
70
|
+
* Bump sqlalchemy from 1.3.23 to 1.4.0 [dependabot[bot]]
|
|
71
|
+
* NOTASK: Bump version [Pavel Liashkov]
|
|
72
|
+
* Bump version [Pavel Liashkov]
|
|
73
|
+
|
|
74
|
+
## 0.5.5 (2021-02-20)
|
|
75
|
+
|
|
76
|
+
* Bump version [Pavel Liashkov]
|
|
77
|
+
* Update Makefile [Pavel Liashkov]
|
|
78
|
+
* Add codecov workflow [Pavel Liashkov]
|
|
79
|
+
* Add codecov badge [Pavel Liashkov]
|
|
80
|
+
* Bump transitions from 0.8.6 to 0.8.7 [dependabot[bot]]
|
|
81
|
+
* Add codecov workflow [Pavel Liashkov]
|
|
82
|
+
* Add codecov badge [Pavel Liashkov]
|
|
83
|
+
* Add codecov settings [Pavel Liashkov]
|
|
84
|
+
* Bump version [Pavel Liashkov]
|
|
85
|
+
|
|
86
|
+
## 0.5.4 (2021-02-15)
|
|
87
|
+
|
|
88
|
+
* Bump transitions from 0.8.6 to 0.8.7 [dependabot[bot]]
|
|
89
|
+
* Bump version [Pavel Liashkov]
|
|
90
|
+
|
|
91
|
+
## 0.5.2 (2021-02-02)
|
|
92
|
+
|
|
93
|
+
* Bump sqlalchemy from 1.3.22 to 1.3.23 [dependabot[bot]]
|
|
94
|
+
* Bump version [Pavel Liashkov]
|
|
95
|
+
|
|
96
|
+
## 0.5.1 (2021-02-01)
|
|
97
|
+
|
|
98
|
+
* Add download count in readme [Pavel Liashkov]
|
|
99
|
+
* Bump version [Pavel Liashkov]
|
|
100
|
+
|
|
101
|
+
## 0.5.0 (2021-02-01)
|
|
102
|
+
|
|
103
|
+
* Fix package format [Pavel Liashkov]
|
|
104
|
+
* Add licence link to readme [Pavel Liashkov]
|
|
105
|
+
* Change readme format [Pavel Liashkov]
|
|
106
|
+
* Add link on security policy [Pavel Liashkov]
|
|
107
|
+
* NOTASK: Bump version [Pavel Liashkov]
|
|
108
|
+
|
|
109
|
+
## 0.4.0 (2021-01-13)
|
|
110
|
+
|
|
111
|
+
* Add state mixin [Pavel Liashkov]
|
|
112
|
+
|
|
113
|
+
## 0.3.3 (2021-01-13)
|
|
114
|
+
|
|
115
|
+
* Update readme [Pavel Liashkov]
|
|
116
|
+
|
|
117
|
+
## 0.3.2 (2021-01-13)
|
|
118
|
+
|
|
119
|
+
* Fix rst error in readme [Pavel Liashkov]
|
|
120
|
+
|
|
121
|
+
## 0.3.1 (2021-01-13)
|
|
122
|
+
|
|
123
|
+
* Fix icon url in readme [Pavel Liashkov]
|
|
124
|
+
|
|
125
|
+
## 0.3.0 (2021-01-13)
|
|
126
|
+
|
|
127
|
+
* Fix github actions [Pavel Liashkov]
|
|
128
|
+
* Add separeted steps lint and tests [Pavel Liashkov]
|
|
129
|
+
* Rename git action job [Pavel Liashkov]
|
|
130
|
+
* Update readme [Pavel Liashkov]
|
|
131
|
+
|
|
132
|
+
## 0.2.1 (2021-01-08)
|
|
133
|
+
|
|
134
|
+
* Added dependency between jobs in workflow [Pavel Liashkov]
|
|
135
|
+
* Bump version [Pavel Liashkov]
|
|
136
|
+
|
|
137
|
+
## 0.2.0 (2021-01-08)
|
|
138
|
+
|
|
139
|
+
* Fix dependabot config [Pavel Liashkov]
|
|
140
|
+
* Fix python version in pyproject [Pavel Liashkov]
|
|
141
|
+
* Fix types [Pavel Liashkov]
|
|
142
|
+
* Add LICENSE, fix setup for pypi [Pavel Liashkov]
|
|
143
|
+
* Bump version [Pavel Liashkov]
|
|
144
|
+
|
|
145
|
+
## 0.1.6 (2021-01-06)
|
|
146
|
+
|
|
147
|
+
* Add config for dependabot [Pavel Liashkov]
|
|
148
|
+
|
|
149
|
+
## 0.1.5 (2021-01-06)
|
|
150
|
+
|
|
151
|
+
* Merge workflow [Pavel Liashkov]
|
|
152
|
+
* Bump version [Pavel Liashkov]
|
|
153
|
+
|
|
154
|
+
## 0.1.3 (2021-01-05)
|
|
155
|
+
|
|
156
|
+
* Fix requires [Pavel Liashkov]
|
|
157
|
+
|
|
158
|
+
## 0.1.2 (2021-01-05)
|
|
159
|
+
|
|
160
|
+
* Fix version settings [Pavel Liashkov]
|
|
161
|
+
|
|
162
|
+
## 0.1.1 (2021-01-05)
|
|
163
|
+
|
|
164
|
+
* Initial commit [Pavel Liashkov]
|
|
165
|
+
* Add base for helper [Pavel Liashkov]
|
|
166
|
+
* Add helper [Pavel Liashkov]
|
|
167
|
+
* Add lint workflow [Pavel Liashkov]
|
|
168
|
+
* Fix lint workflow [Pavel Liashkov]
|
|
169
|
+
* Add tests [Pavel Liashkov]
|
|
170
|
+
* Add test workflow [Pavel Liashkov]
|
|
171
|
+
* Fix error after linter [Pavel Liashkov]
|
|
172
|
+
* Add publish workflow [Pavel Liashkov]
|
|
173
|
+
* Fix github workflow [Pavel Liashkov]
|
|
174
|
+
* Fix publish workflow [Pavel Liashkov]
|
|
175
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Project configuration
|
|
3
|
+
# =============================================================================
|
|
4
|
+
PROJECT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
|
5
|
+
PROJECT_NAME := sqlalchemy-state-machine
|
|
6
|
+
SRC_PATH := src/sqlalchemy_state_machine
|
|
7
|
+
LINT_PATHS := src tests examples
|
|
8
|
+
UV ?= uv
|
|
9
|
+
|
|
10
|
+
.DEFAULT_GOAL := help
|
|
11
|
+
|
|
12
|
+
.PHONY: help venv/install/main venv/install/all lint/ruff lint/mypy lint format clean test build sys/changelog sys/tag
|
|
13
|
+
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# Help
|
|
16
|
+
# =============================================================================
|
|
17
|
+
help: ## Display this help message
|
|
18
|
+
@awk 'BEGIN { FS = ":.*##"; printf "\nUsage:\n make <target>\n\nTargets:\n" } /^[a-zA-Z0-9_./-]+:.*##/ { printf " \033[36m%-24s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Environment
|
|
22
|
+
# =============================================================================
|
|
23
|
+
venv/install/main: ## Install main dependencies
|
|
24
|
+
@echo "==> Installing $(PROJECT_NAME) main dependencies"
|
|
25
|
+
$(UV) sync --no-group dev
|
|
26
|
+
|
|
27
|
+
venv/install/all: ## Install all dependency groups
|
|
28
|
+
@echo "==> Installing $(PROJECT_NAME) with all dependency groups"
|
|
29
|
+
$(UV) sync --all-groups
|
|
30
|
+
|
|
31
|
+
# =============================================================================
|
|
32
|
+
# Code quality
|
|
33
|
+
# =============================================================================
|
|
34
|
+
lint/ruff: ## Check formatting and lint with Ruff
|
|
35
|
+
@echo "==> Checking formatting and linting with Ruff"
|
|
36
|
+
$(UV) run --locked ruff format --check $(LINT_PATHS)
|
|
37
|
+
$(UV) run --locked ruff check $(LINT_PATHS)
|
|
38
|
+
|
|
39
|
+
lint/mypy: ## Type-check the source package with mypy
|
|
40
|
+
@echo "==> Type-checking $(SRC_PATH) with mypy"
|
|
41
|
+
$(UV) run --locked mypy $(SRC_PATH)
|
|
42
|
+
|
|
43
|
+
lint: lint/ruff lint/mypy ## Run all lint checks
|
|
44
|
+
@echo "==> Lint checks passed"
|
|
45
|
+
|
|
46
|
+
format: ## Format and auto-fix lint issues
|
|
47
|
+
@echo "==> Formatting and auto-fixing lint issues"
|
|
48
|
+
$(UV) run --locked ruff format $(LINT_PATHS)
|
|
49
|
+
$(UV) run --locked ruff check --fix $(LINT_PATHS)
|
|
50
|
+
|
|
51
|
+
# =============================================================================
|
|
52
|
+
# Test and build
|
|
53
|
+
# =============================================================================
|
|
54
|
+
test: ## Run the test suite with coverage
|
|
55
|
+
@echo "==> Testing $(PROJECT_NAME) with coverage"
|
|
56
|
+
$(UV) run --locked pytest --cov=sqlalchemy_state_machine --cov-report=term-missing
|
|
57
|
+
|
|
58
|
+
build: ## Build distribution packages
|
|
59
|
+
@echo "==> Building $(PROJECT_NAME) distribution packages"
|
|
60
|
+
$(UV) build
|
|
61
|
+
|
|
62
|
+
clean: ## Remove generated files and caches
|
|
63
|
+
@echo "==> Removing generated files and caches"
|
|
64
|
+
rm -rf .coverage .mypy_cache .pytest_cache .ruff_cache build dist
|
|
65
|
+
find . -type d -name __pycache__ -prune -exec rm -rf {} +
|
|
66
|
+
|
|
67
|
+
# =============================================================================
|
|
68
|
+
# Release automation
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# Requires mktemp, available on the project's supported macOS and Linux development environments.
|
|
71
|
+
sys/changelog: ## Generate CHANGELOG.md from historical version tags
|
|
72
|
+
@echo "==> Generating CHANGELOG.md from version tags"
|
|
73
|
+
@tmp_file=$$(mktemp CHANGELOG.md.XXXXXX) || exit $$?; \
|
|
74
|
+
cleanup() { rm -f "$$tmp_file"; }; \
|
|
75
|
+
trap cleanup 0; \
|
|
76
|
+
trap 'exit 1' HUP INT TERM; \
|
|
77
|
+
tags="$$( $(UV) run --locked python -c 'import re, subprocess, sys; pattern = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$$"); raw = subprocess.check_output(["git", "tag"], text=True).splitlines(); tagged = [(tuple(map(int, match.groups())), tag) for tag in raw if (match := pattern.fullmatch(tag))]; len({version for version, _ in tagged}) == len(tagged) or sys.exit("duplicate semantic-version tags are not supported"); tagged.sort(reverse=True); print(" ".join(tag for _, tag in tagged))' )" || exit $$?; \
|
|
78
|
+
if [ -z "$$tags" ]; then \
|
|
79
|
+
echo "No semantic-version tags found." >&2; \
|
|
80
|
+
exit 1; \
|
|
81
|
+
fi; \
|
|
82
|
+
{ \
|
|
83
|
+
printf '# Changelog\n\n'; \
|
|
84
|
+
set -- $$tags; \
|
|
85
|
+
newest_tag=$$1; \
|
|
86
|
+
unreleased=$$(git log --no-merges "$$newest_tag..HEAD" --format='* %s [%an]' --reverse) || exit $$?; \
|
|
87
|
+
if [ -n "$$unreleased" ]; then \
|
|
88
|
+
printf '## Unreleased\n\n%s\n\n' "$$unreleased"; \
|
|
89
|
+
fi; \
|
|
90
|
+
while [ $$# -gt 0 ]; do \
|
|
91
|
+
current_tag=$$1; \
|
|
92
|
+
shift; \
|
|
93
|
+
older_tag=$${1:-}; \
|
|
94
|
+
tag_date=$$(git log -1 --format=%as "$$current_tag") || exit $$?; \
|
|
95
|
+
display_tag=$${current_tag#v}; \
|
|
96
|
+
printf '## %s (%s)\n\n' "$$display_tag" "$$tag_date"; \
|
|
97
|
+
if [ -n "$$older_tag" ]; then range="$$older_tag..$$current_tag"; else range="$$current_tag"; fi; \
|
|
98
|
+
git log --no-merges "$$range" --format='* %s [%an]' --reverse || exit $$?; \
|
|
99
|
+
printf '\n'; \
|
|
100
|
+
done; \
|
|
101
|
+
} > "$$tmp_file" || exit $$?; \
|
|
102
|
+
if ! mv "$$tmp_file" CHANGELOG.md; then \
|
|
103
|
+
echo "Unable to replace CHANGELOG.md." >&2; \
|
|
104
|
+
exit 1; \
|
|
105
|
+
fi; \
|
|
106
|
+
trap - 0; \
|
|
107
|
+
echo "CHANGELOG.md generated successfully."
|
|
108
|
+
|
|
109
|
+
sys/tag: ## Create and push a vX.Y.Z release tag
|
|
110
|
+
@read -r -p "Enter tag version (e.g., 2.0.0): " TAG; \
|
|
111
|
+
if ! printf '%s\n' "$$TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$$'; then \
|
|
112
|
+
echo "Tag version must use the X.Y.Z format (for example, 2.0.0)" >&2; \
|
|
113
|
+
exit 1; \
|
|
114
|
+
fi; \
|
|
115
|
+
project_version="$$( $(UV) run --locked python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])' )" || exit $$?; \
|
|
116
|
+
if [ "$$project_version" != "$$TAG" ]; then \
|
|
117
|
+
echo "Tag version $$TAG does not match pyproject.toml project.version $$project_version" >&2; \
|
|
118
|
+
exit 1; \
|
|
119
|
+
fi; \
|
|
120
|
+
if [ -n "$$(git status --porcelain --untracked-files=all)" ]; then \
|
|
121
|
+
echo "Worktree must be clean before creating a release tag." >&2; \
|
|
122
|
+
exit 1; \
|
|
123
|
+
fi; \
|
|
124
|
+
if git show-ref --verify --quiet "refs/tags/v$$TAG"; then \
|
|
125
|
+
echo "Local tag v$$TAG already exists." >&2; \
|
|
126
|
+
exit 1; \
|
|
127
|
+
fi; \
|
|
128
|
+
echo "==> Creating and pushing v$$TAG"; \
|
|
129
|
+
git tag -a "v$$TAG" -m "Release v$$TAG" || exit $$?; \
|
|
130
|
+
if ! git push origin "v$$TAG"; then \
|
|
131
|
+
echo "Push failed; deleting local tag v$$TAG." >&2; \
|
|
132
|
+
git tag -d "v$$TAG" || exit $$?; \
|
|
133
|
+
exit 1; \
|
|
134
|
+
fi
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sqlalchemy-state-machine
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Explicit, typed state transitions for SQLAlchemy models
|
|
5
|
+
Project-URL: Changelog, https://github.com/bigbag/sqlalchemy-state-machine/blob/main/CHANGELOG.md
|
|
6
|
+
Project-URL: Homepage, https://github.com/bigbag/sqlalchemy-state-machine
|
|
7
|
+
Project-URL: Issues, https://github.com/bigbag/sqlalchemy-state-machine/issues
|
|
8
|
+
Author-email: Pavel Liashkov <pavel.liashkov@protonmail.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: sqlalchemy,state-machine,transitions,workflow
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: sqlalchemy<3.0.0,>=2.0.51
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# sqlalchemy-state-machine
|
|
28
|
+
|
|
29
|
+
Explicit, typed state transitions for SQLAlchemy models.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Python 3.11+ (CI validates Python 3.11, 3.12, 3.13, 3.14, and 3.15)
|
|
34
|
+
- SQLAlchemy 2.x
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
uv add sqlalchemy-state-machine
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```console
|
|
43
|
+
pip install sqlalchemy-state-machine
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
Declare a SQLAlchemy 2.x model and its state machine together:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from typing import ClassVar
|
|
52
|
+
|
|
53
|
+
from sqlalchemy import create_engine
|
|
54
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
|
|
55
|
+
|
|
56
|
+
from sqlalchemy_state_machine import StateMachine, StateMachineMixin, Transition
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Base(DeclarativeBase):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Order(StateMachineMixin, Base):
|
|
64
|
+
__tablename__ = "orders"
|
|
65
|
+
|
|
66
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
67
|
+
status: Mapped[str] = mapped_column(nullable=False)
|
|
68
|
+
|
|
69
|
+
state_machine: ClassVar[StateMachine] = StateMachine(
|
|
70
|
+
state_attribute="status",
|
|
71
|
+
initial="draft",
|
|
72
|
+
transitions=(
|
|
73
|
+
Transition(name="submit", source="draft", target="submitted"),
|
|
74
|
+
Transition(
|
|
75
|
+
name="cancel",
|
|
76
|
+
source=("draft", "submitted"),
|
|
77
|
+
target="cancelled",
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
engine = create_engine("sqlite://")
|
|
84
|
+
Base.metadata.create_all(engine)
|
|
85
|
+
|
|
86
|
+
with Session(engine) as session:
|
|
87
|
+
order = Order()
|
|
88
|
+
assert order.status == "draft"
|
|
89
|
+
assert order.can_transition("submit")
|
|
90
|
+
assert order.available_transitions() == ("submit", "cancel")
|
|
91
|
+
assert order.transition("submit") == "submitted"
|
|
92
|
+
|
|
93
|
+
session.add(order)
|
|
94
|
+
session.commit()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`transition(name)` applies a declared transition and returns its target state.
|
|
98
|
+
`can_transition(name)` reports whether a transition is currently available, and
|
|
99
|
+
`available_transitions()` returns available names in declaration order. Calling
|
|
100
|
+
an unknown or unavailable transition raises `InvalidTransitionError`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from sqlalchemy_state_machine import InvalidTransitionError
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
order.transition("submit")
|
|
107
|
+
except InvalidTransitionError:
|
|
108
|
+
pass
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Callbacks and transactions
|
|
112
|
+
|
|
113
|
+
A `Transition` can define `before` and `after` callbacks. Both receive
|
|
114
|
+
`(model, transition)`. The `before` callback runs while the model has its
|
|
115
|
+
source state, the state is then changed, and the `after` callback runs with the
|
|
116
|
+
target state. If either callback raises, the model state is restored to its
|
|
117
|
+
original value and the exception is re-raised.
|
|
118
|
+
|
|
119
|
+
The library changes only the mapped state attribute. It owns no SQLAlchemy
|
|
120
|
+
session `flush`, `commit`, or `rollback`; application code controls transaction
|
|
121
|
+
boundaries.
|
|
122
|
+
|
|
123
|
+
## Migrating from 1.x
|
|
124
|
+
|
|
125
|
+
2.0 is a breaking rewrite for Python 3.11+ and SQLAlchemy 2.x.
|
|
126
|
+
|
|
127
|
+
| 1.x | 2.0 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `StateConfig` | `StateMachine` and `Transition` |
|
|
130
|
+
| `StateMixin` | `StateMachineMixin` |
|
|
131
|
+
| Manual SQLAlchemy init/load events | No longer needed |
|
|
132
|
+
| Dynamic `model.set_sent()` | `model.transition("set_sent")` |
|
|
133
|
+
| Redundant declared states | Inferred from the initial state and transitions |
|
|
134
|
+
| `after_state_change` | Per-transition `before` and `after` callbacks |
|
|
135
|
+
|
|
136
|
+
The legacy API and its `transitions` dependency were removed; update models to
|
|
137
|
+
the explicit 2.0 API rather than mixing 1.x declarations with 2.0 classes.
|
|
138
|
+
|
|
139
|
+
## Example
|
|
140
|
+
|
|
141
|
+
Run the standalone example:
|
|
142
|
+
|
|
143
|
+
```console
|
|
144
|
+
uv run python examples/basic_usage.py
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```console
|
|
150
|
+
uv sync
|
|
151
|
+
uv run ruff format --check src tests examples
|
|
152
|
+
uv run ruff check src tests examples
|
|
153
|
+
uv run mypy src
|
|
154
|
+
uv run pytest --cov=sqlalchemy_state_machine
|
|
155
|
+
uv build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
sqlalchemy-state-machine is distributed under the Apache License 2.0.
|