django-queuebie 0.3.8__tar.gz → 0.3.10__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 (79) hide show
  1. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.ambient-package-update/metadata.py +51 -51
  2. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.ambient-package-update/templates/snippets/content.tpl +53 -53
  3. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.ambient-package-update/templates/snippets/tagline.tpl +1 -1
  4. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.editorconfig +17 -17
  5. django_queuebie-0.3.10/.github/workflows/ci.yml +11 -0
  6. django_queuebie-0.3.8/.github/workflows/ci.yml → django_queuebie-0.3.10/.github/workflows/quality-gate.yml +91 -94
  7. django_queuebie-0.3.10/.github/workflows/release.yml +136 -0
  8. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.gitignore +160 -160
  9. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.pre-commit-config.yaml +59 -59
  10. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/.readthedocs.yaml +31 -31
  11. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/CHANGES.md +40 -34
  12. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/CONTRIBUTING.md +75 -75
  13. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/LICENSE +21 -21
  14. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/LICENSE.md +21 -21
  15. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/MANIFEST.in +4 -4
  16. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/PKG-INFO +27 -36
  17. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/README.md +128 -138
  18. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/SECURITY.md +18 -18
  19. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/Makefile +20 -20
  20. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/conf.py +81 -81
  21. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/features/changelog.rst +2 -2
  22. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/features/getting-started.md +98 -98
  23. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/features/introduction.md +53 -53
  24. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/features/motivation.md +30 -30
  25. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/features/settings.md +41 -41
  26. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/features/setup.md +41 -41
  27. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/index.rst +21 -21
  28. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/docs/make.bat +35 -35
  29. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/manage.py +23 -23
  30. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/pyproject.toml +274 -270
  31. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/__init__.py +8 -8
  32. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/apps.py +15 -15
  33. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/database_blocker.py +25 -25
  34. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/exceptions.py +17 -17
  35. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/logger.py +10 -10
  36. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/management/commands/clear_queuebie_registry.py +13 -13
  37. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/messages.py +39 -39
  38. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/registry.py +153 -153
  39. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/runner.py +63 -63
  40. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/settings.py +32 -32
  41. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/utils.py +30 -30
  42. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/scripts/unix/install_requirements.sh +3 -3
  43. django_queuebie-0.3.10/scripts/unix/publish_to_pypi.sh +4 -0
  44. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/scripts/windows/install_requirements.ps1 +2 -2
  45. django_queuebie-0.3.10/scripts/windows/publish_to_pypi.ps1 +3 -0
  46. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/settings.py +79 -79
  47. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/handlers/commands/testapp.py +55 -55
  48. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/handlers/events/testapp.py +20 -20
  49. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/messages/commands/my_commands.py +33 -33
  50. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/messages/commands/other_commands.py +8 -8
  51. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/messages/events/my_events.py +18 -18
  52. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/urls.py +7 -7
  53. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/helpers/commands.py +8 -8
  54. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/helpers/events.py +8 -8
  55. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/management_commands/test_clear_queuebie_registry.py +27 -27
  56. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_apps.py +25 -25
  57. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_database_blocker.py +17 -17
  58. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_exceptions.py +23 -23
  59. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_logger.py +11 -11
  60. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_messages.py +16 -16
  61. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_registry.py +208 -208
  62. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_runner.py +138 -138
  63. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_settings.py +45 -45
  64. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/test_utils.py +38 -38
  65. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/uv.lock +1233 -996
  66. django_queuebie-0.3.8/scripts/unix/publish_to_pypi.sh +0 -4
  67. django_queuebie-0.3.8/scripts/windows/publish_to_pypi.ps1 +0 -3
  68. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/management/__init__.py +0 -0
  69. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/queuebie/management/commands/__init__.py +0 -0
  70. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/__init__.py +0 -0
  71. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/handlers/__init__.py +0 -0
  72. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/handlers/commands/__init__.py +0 -0
  73. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/handlers/events/__init__.py +0 -0
  74. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/messages/__init__.py +0 -0
  75. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/messages/commands/__init__.py +0 -0
  76. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/testapp/messages/events/__init__.py +0 -0
  77. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/__init__.py +0 -0
  78. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/helpers/__init__.py +0 -0
  79. {django_queuebie-0.3.8 → django_queuebie-0.3.10}/tests/management_commands/__init__.py +0 -0
@@ -1,51 +1,51 @@
1
- from ambient_package_update.metadata.author import PackageAuthor
2
- from ambient_package_update.metadata.constants import (
3
- DEPLOYMENT_STATUS_BETA,
4
- DEV_DEPENDENCIES,
5
- LICENSE_MIT,
6
- SUPPORTED_DJANGO_VERSIONS,
7
- )
8
- from ambient_package_update.metadata.maintainer import PackageMaintainer
9
- from ambient_package_update.metadata.package import PackageMetadata
10
- from ambient_package_update.metadata.readme import ReadmeContent
11
- from ambient_package_update.metadata.ruff_ignored_inspection import RuffIgnoredInspection
12
-
13
- METADATA = PackageMetadata(
14
- package_name="django_queuebie",
15
- module_name="queuebie",
16
- github_package_group="ambient-innovation",
17
- authors=[
18
- PackageAuthor(
19
- name="Ambient Digital",
20
- email="hello@ambient.digital",
21
- ),
22
- ],
23
- maintainer=PackageMaintainer(name="Ambient Digital", url="https://ambient.digital/", email="hello@ambient.digital"),
24
- licenser="Ambient Innovation: GmbH",
25
- license=LICENSE_MIT,
26
- license_year=2025,
27
- development_status=DEPLOYMENT_STATUS_BETA,
28
- has_migrations=False,
29
- main_branch="main",
30
- claim="A simple and synchronous message queue for commands and events for Django",
31
- readme_content=ReadmeContent(uses_internationalisation=True),
32
- dependencies=[
33
- f"Django>={SUPPORTED_DJANGO_VERSIONS[0]}",
34
- ],
35
- supported_django_versions=SUPPORTED_DJANGO_VERSIONS,
36
- supported_python_versions=[
37
- "3.10",
38
- "3.11",
39
- "3.12",
40
- "3.13",
41
- ],
42
- optional_dependencies={
43
- "dev": [
44
- *DEV_DEPENDENCIES,
45
- ],
46
- },
47
- ruff_ignore_list=[
48
- RuffIgnoredInspection(key="TD002", comment="Missing author in TODO"),
49
- RuffIgnoredInspection(key="TD003", comment="Missing issue link on the line following this TODO"),
50
- ],
51
- )
1
+ from ambient_package_update.metadata.author import PackageAuthor
2
+ from ambient_package_update.metadata.constants import (
3
+ DEPLOYMENT_STATUS_BETA,
4
+ DEV_DEPENDENCIES,
5
+ LICENSE_MIT,
6
+ SUPPORTED_DJANGO_VERSIONS,
7
+ )
8
+ from ambient_package_update.metadata.maintainer import PackageMaintainer
9
+ from ambient_package_update.metadata.package import PackageMetadata
10
+ from ambient_package_update.metadata.readme import ReadmeContent
11
+ from ambient_package_update.metadata.ruff_ignored_inspection import RuffIgnoredInspection
12
+
13
+ METADATA = PackageMetadata(
14
+ package_name="django_queuebie",
15
+ module_name="queuebie",
16
+ github_package_group="ambient-innovation",
17
+ authors=[
18
+ PackageAuthor(
19
+ name="Ambient Digital",
20
+ email="hello@ambient.digital",
21
+ ),
22
+ ],
23
+ maintainer=PackageMaintainer(name="Ambient Digital", url="https://ambient.digital/", email="hello@ambient.digital"),
24
+ licenser="Ambient Innovation: GmbH",
25
+ license=LICENSE_MIT,
26
+ license_year=2025,
27
+ development_status=DEPLOYMENT_STATUS_BETA,
28
+ has_migrations=False,
29
+ main_branch="main",
30
+ claim="A simple and synchronous message queue for commands and events for Django",
31
+ readme_content=ReadmeContent(uses_internationalisation=True),
32
+ dependencies=[
33
+ f"Django>={SUPPORTED_DJANGO_VERSIONS[0]}",
34
+ ],
35
+ supported_django_versions=SUPPORTED_DJANGO_VERSIONS,
36
+ supported_python_versions=[
37
+ "3.10",
38
+ "3.11",
39
+ "3.12",
40
+ "3.13",
41
+ ],
42
+ optional_dependencies={
43
+ "dev": [
44
+ *DEV_DEPENDENCIES,
45
+ ],
46
+ },
47
+ ruff_ignore_list=[
48
+ RuffIgnoredInspection(key="TD002", comment="Missing author in TODO"),
49
+ RuffIgnoredInspection(key="TD003", comment="Missing issue link on the line following this TODO"),
50
+ ],
51
+ )
@@ -1,53 +1,53 @@
1
- ## Features
2
-
3
- * Split up your business logic in commands and events
4
- * Commands are imperatives telling your system what to do, events reflect that something has happened
5
- * Register light-weight functions via a decorator to listen to your commands and events
6
- * Message handlers receive the context of the message (command or event), providing an explicit API
7
- * No magic, no side effects since the queue works synchronously
8
-
9
- ```python
10
- import dataclasses
11
-
12
- from queuebie.runner import handle_message
13
- from queuebie.messages import Command, Event
14
- from queuebie import message_registry
15
-
16
-
17
- # Example command
18
- @dataclasses.dataclass(kw_only=True)
19
- class BuyProduct(Command):
20
- product_id: int
21
- customer_id: int
22
- price: float
23
- currency: str
24
-
25
-
26
- # Example event
27
- @dataclasses.dataclass(kw_only=True)
28
- class ProductBought(Event):
29
- product_id: int
30
- customer_id: int
31
-
32
-
33
- # Example handler
34
- @message_registry.register_command(BuyProduct)
35
- def handle_buy_product(context: BuyProduct) -> Event:
36
- # Here lives your business logic
37
-
38
- return ProductBought(
39
- product_id=context.product_id,
40
- customer_id=context.customer_id,
41
- )
42
-
43
-
44
- # Start queue and process messages
45
- handle_message(
46
- BuyProduct(
47
- product_id=product.id,
48
- customer_id=customer.id,
49
- price=12.99,
50
- currency="EUR",
51
- )
52
- )
53
- ```
1
+ ## Features
2
+
3
+ * Split up your business logic in commands and events
4
+ * Commands are imperatives telling your system what to do, events reflect that something has happened
5
+ * Register light-weight functions via a decorator to listen to your commands and events
6
+ * Message handlers receive the context of the message (command or event), providing an explicit API
7
+ * No magic, no side effects since the queue works synchronously
8
+
9
+ ```python
10
+ import dataclasses
11
+
12
+ from queuebie.runner import handle_message
13
+ from queuebie.messages import Command, Event
14
+ from queuebie import message_registry
15
+
16
+
17
+ # Example command
18
+ @dataclasses.dataclass(kw_only=True)
19
+ class BuyProduct(Command):
20
+ product_id: int
21
+ customer_id: int
22
+ price: float
23
+ currency: str
24
+
25
+
26
+ # Example event
27
+ @dataclasses.dataclass(kw_only=True)
28
+ class ProductBought(Event):
29
+ product_id: int
30
+ customer_id: int
31
+
32
+
33
+ # Example handler
34
+ @message_registry.register_command(BuyProduct)
35
+ def handle_buy_product(context: BuyProduct) -> Event:
36
+ # Here lives your business logic
37
+
38
+ return ProductBought(
39
+ product_id=context.product_id,
40
+ customer_id=context.customer_id,
41
+ )
42
+
43
+
44
+ # Start queue and process messages
45
+ handle_message(
46
+ BuyProduct(
47
+ product_id=product.id,
48
+ customer_id=customer.id,
49
+ price=12.99,
50
+ currency="EUR",
51
+ )
52
+ )
53
+ ```
@@ -1 +1 @@
1
- A simple and synchronous message queue for commands and events for Django.
1
+ A simple and synchronous message queue for commands and events for Django.
@@ -1,17 +1,17 @@
1
- # http://editorconfig.org
2
-
3
- root = true
4
-
5
- [*]
6
- charset = utf-8
7
- end_of_line = lf
8
- insert_final_newline = true
9
- trim_trailing_whitespace = true
10
-
11
- [*.{py,rst,ini}]
12
- indent_style = space
13
- indent_size = 4
14
-
15
- [*.yml]
16
- indent_style = space
17
- indent_size = 2
1
+ # http://editorconfig.org
2
+
3
+ root = true
4
+
5
+ [*]
6
+ charset = utf-8
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.{py,rst,ini}]
12
+ indent_style = space
13
+ indent_size = 4
14
+
15
+ [*.yml]
16
+ indent_style = space
17
+ indent_size = 2
@@ -0,0 +1,11 @@
1
+ name: Unit tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ ci:
11
+ uses: ./.github/workflows/quality-gate.yml
@@ -1,94 +1,91 @@
1
- name: Unit tests
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- pull_request:
8
-
9
- jobs:
10
- linting:
11
- runs-on: ubuntu-24.04
12
- steps:
13
- - uses: actions/checkout@v4
14
-
15
- - name: Set up Python 3.13
16
- uses: actions/setup-python@v5
17
- with:
18
- python-version: "3.13"
19
-
20
- - name: Install required packages
21
- run: pip install pre-commit
22
-
23
- - name: Run pre-commit hooks
24
- run: pre-commit run --all-files
25
-
26
- tests:
27
- name: Python ${{ matrix.python-version }}, django ${{ matrix.django-version }}
28
- runs-on: ubuntu-24.04
29
- strategy:
30
- matrix:
31
- python-version: ['3.10', '3.11', '3.12', '3.13', ]
32
- django-version: ['42', '52', '60', ]
33
-
34
- exclude:
35
- - python-version: '3.11'
36
- django-version: 60
37
- - python-version: '3.10'
38
- django-version: 60
39
- - python-version: '3.9'
40
- django-version: 52
41
- - python-version: "3.13"
42
- django-version: 42
43
-
44
- steps:
45
- - uses: actions/checkout@v4
46
- - name: setup python
47
- uses: actions/setup-python@v5
48
- with:
49
- python-version: ${{ matrix.python-version }}
50
- - name: Install uv
51
- uses: astral-sh/setup-uv@v5
52
- - name: Install tox
53
- run: uv pip install --system tox tox-uv
54
- - name: Run Tests
55
- env:
56
- TOXENV: django${{ matrix.django-version }}
57
- run: tox
58
- - name: Upload coverage data
59
- uses: actions/upload-artifact@v4
60
- with:
61
- name: coverage-data-${{ matrix.python-version }}-${{ matrix.django-version }}
62
- path: '${{ github.workspace }}/.coverage'
63
- include-hidden-files: true
64
- if-no-files-found: error
65
-
66
- coverage:
67
- name: Coverage
68
- runs-on: ubuntu-24.04
69
- needs: tests
70
- steps:
71
- - uses: actions/checkout@v4
72
-
73
- - uses: actions/setup-python@v5
74
- with:
75
- python-version: '3.13'
76
-
77
- - name: Install dependencies
78
- run: python -m pip install --upgrade coverage[toml]
79
-
80
- - name: Download data
81
- uses: actions/download-artifact@v4
82
- with:
83
- path: ${{ github.workspace }}/coverage-reports
84
- pattern: coverage-data-*
85
- merge-multiple: false
86
-
87
- - name: Combine coverage and fail if it's <100.0%
88
- run: |
89
- python -m coverage combine coverage-reports/*/.coverage
90
- python -m coverage xml
91
- python -m coverage html --skip-covered --skip-empty
92
- python -m coverage report --fail-under=100.0
93
- echo "## Coverage summary" >> $GITHUB_STEP_SUMMARY
94
- python -m coverage report --format=markdown >> $GITHUB_STEP_SUMMARY
1
+ name: CI jobs
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ jobs:
7
+ linting:
8
+ runs-on: ubuntu-24.04
9
+ steps:
10
+ - uses: actions/checkout@v6
11
+
12
+ - name: Set up Python 3.13
13
+ uses: actions/setup-python@v6
14
+ with:
15
+ python-version: "3.13"
16
+
17
+ - name: Install required packages
18
+ run: pip install pre-commit
19
+
20
+ - name: Run pre-commit hooks
21
+ run: pre-commit run --all-files
22
+
23
+ tests:
24
+ name: Python ${{ matrix.python-version }}, django ${{ matrix.django-version }}
25
+ runs-on: ubuntu-24.04
26
+ strategy:
27
+ matrix:
28
+ python-version: ['3.10', '3.11', '3.12', '3.13', ]
29
+ django-version: ['42', '52', '60', ]
30
+
31
+ exclude:
32
+ - python-version: '3.11'
33
+ django-version: 60
34
+ - python-version: '3.10'
35
+ django-version: 60
36
+ - python-version: '3.9'
37
+ django-version: 52
38
+ - python-version: "3.13"
39
+ django-version: 42
40
+
41
+ steps:
42
+ - uses: actions/checkout@v6
43
+ - name: setup python
44
+ uses: actions/setup-python@v6
45
+ with:
46
+ python-version: ${{ matrix.python-version }}
47
+ - name: Install uv
48
+ uses: astral-sh/setup-uv@v8.0.0
49
+ - name: Install tox
50
+ run: uv pip install --system tox tox-uv
51
+ - name: Run Tests
52
+ env:
53
+ TOXENV: django${{ matrix.django-version }}
54
+ run: tox
55
+ - name: Upload coverage data
56
+ uses: actions/upload-artifact@v7
57
+ with:
58
+ name: coverage-data-${{ matrix.python-version }}-${{ matrix.django-version }}
59
+ path: '${{ github.workspace }}/.coverage'
60
+ include-hidden-files: true
61
+ if-no-files-found: error
62
+
63
+ coverage:
64
+ name: Coverage
65
+ runs-on: ubuntu-24.04
66
+ needs: tests
67
+ steps:
68
+ - uses: actions/checkout@v6
69
+
70
+ - uses: actions/setup-python@v6
71
+ with:
72
+ python-version: '3.13'
73
+
74
+ - name: Install dependencies
75
+ run: python -m pip install --upgrade coverage[toml]
76
+
77
+ - name: Download data
78
+ uses: actions/download-artifact@v8
79
+ with:
80
+ path: ${{ github.workspace }}/coverage-reports
81
+ pattern: coverage-data-*
82
+ merge-multiple: false
83
+
84
+ - name: Combine coverage and fail if it's <100.0%
85
+ run: |
86
+ python -m coverage combine coverage-reports/*/.coverage
87
+ python -m coverage xml
88
+ python -m coverage html --skip-covered --skip-empty
89
+ python -m coverage report --fail-under=100.0
90
+ echo "## Coverage summary" >> $GITHUB_STEP_SUMMARY
91
+ python -m coverage report --format=markdown >> $GITHUB_STEP_SUMMARY
@@ -0,0 +1,136 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ # Restrictive default — each job declares only what it needs
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ ci:
14
+ uses: ./.github/workflows/quality-gate.yml
15
+
16
+ check-branch:
17
+ name: Verify tag is on main
18
+ runs-on: ubuntu-24.04
19
+ outputs:
20
+ on-main: ${{ steps.check.outputs.on-main }}
21
+ steps:
22
+ - uses: actions/checkout@v6
23
+ with:
24
+ fetch-depth: 0
25
+ - name: Check if tag is on main
26
+ id: check
27
+ run: |
28
+ git fetch origin main
29
+ if git merge-base --is-ancestor "${{ github.sha }}" origin/main; then
30
+ echo "on-main=true" >> $GITHUB_OUTPUT
31
+ else
32
+ echo "Tag is not on main, skipping release." >&2
33
+ echo "on-main=false" >> $GITHUB_OUTPUT
34
+ fi
35
+
36
+ build:
37
+ name: Build distribution packages
38
+ runs-on: ubuntu-24.04
39
+ needs: [ci, check-branch]
40
+ if: needs.check-branch.outputs.on-main == 'true'
41
+ permissions:
42
+ contents: read
43
+ steps:
44
+ - uses: actions/checkout@v6
45
+
46
+ - name: Set up Python 3.13
47
+ uses: actions/setup-python@v6
48
+ with:
49
+ python-version: "3.13"
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v8.0.0
53
+
54
+ - name: Build packages
55
+ run: uv build
56
+
57
+ - name: Upload distribution packages
58
+ uses: actions/upload-artifact@v7
59
+ with:
60
+ name: dist
61
+ path: dist/
62
+ if-no-files-found: error
63
+
64
+ sign:
65
+ name: Sign distribution packages
66
+ runs-on: ubuntu-24.04
67
+ needs: build
68
+ permissions:
69
+ id-token: write # required for Sigstore OIDC signing
70
+ contents: read
71
+ steps:
72
+ - name: Download distribution packages
73
+ uses: actions/download-artifact@v8
74
+ with:
75
+ name: dist
76
+ path: dist/
77
+
78
+ - name: Sign packages with Sigstore
79
+ uses: sigstore/gh-action-sigstore-python@v3.3.0
80
+ with:
81
+ inputs: >-
82
+ ./dist/*.tar.gz
83
+ ./dist/*.whl
84
+
85
+ - name: Upload signed distribution packages
86
+ uses: actions/upload-artifact@v7
87
+ with:
88
+ name: dist-signed
89
+ path: dist/
90
+ if-no-files-found: error
91
+
92
+ github-release:
93
+ name: Create GitHub Release
94
+ runs-on: ubuntu-24.04
95
+ needs: sign
96
+ permissions:
97
+ contents: write # required to create GitHub releases
98
+ steps:
99
+ - name: Download signed distribution packages
100
+ uses: actions/download-artifact@v8
101
+ with:
102
+ name: dist-signed
103
+ path: dist/
104
+
105
+ - name: Create GitHub Release
106
+ env:
107
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108
+ run: |
109
+ shopt -s failglob
110
+ gh release create "${{ github.ref_name }}" \
111
+ --repo "${{ github.repository }}" \
112
+ --generate-notes \
113
+ dist/*
114
+
115
+ publish-pypi:
116
+ name: Publish to PyPI
117
+ runs-on: ubuntu-24.04
118
+ needs: sign
119
+ environment: pypi
120
+ permissions:
121
+ id-token: write # required for PyPI Trusted Publisher OIDC auth
122
+ contents: read
123
+ steps:
124
+ - name: Download signed distribution packages
125
+ uses: actions/download-artifact@v8
126
+ with:
127
+ name: dist-signed
128
+ path: dist/
129
+
130
+ - name: Remove Sigstore bundles (PyPI only accepts .whl and .tar.gz)
131
+ run: rm dist/*.sigstore.json
132
+
133
+ - name: Publish to PyPI
134
+ uses: pypa/gh-action-pypi-publish@release/v1
135
+ # Sigstore attestations are generated and uploaded to PyPI automatically
136
+ # when using Trusted Publishing — no extra configuration needed.