isotope-pattern-lib 1.0.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 (49) hide show
  1. isotope_pattern_lib-1.0.0/.github/scripts/compute-next-version.sh +42 -0
  2. isotope_pattern_lib-1.0.0/.github/workflows/ci-branch.yaml +121 -0
  3. isotope_pattern_lib-1.0.0/.github/workflows/ci-master.yaml +181 -0
  4. isotope_pattern_lib-1.0.0/.github/workflows/ci-pull-request.yaml +209 -0
  5. isotope_pattern_lib-1.0.0/.gitignore +9 -0
  6. isotope_pattern_lib-1.0.0/PKG-INFO +161 -0
  7. isotope_pattern_lib-1.0.0/README.md +143 -0
  8. isotope_pattern_lib-1.0.0/isotope_pattern_lib/__init__.py +0 -0
  9. isotope_pattern_lib-1.0.0/isotope_pattern_lib/api.py +33 -0
  10. isotope_pattern_lib-1.0.0/isotope_pattern_lib/core/__init__.py +0 -0
  11. isotope_pattern_lib-1.0.0/isotope_pattern_lib/core/formula_parser.py +30 -0
  12. isotope_pattern_lib-1.0.0/isotope_pattern_lib/core/isotope_pattern.py +51 -0
  13. isotope_pattern_lib-1.0.0/isotope_pattern_lib/resources/config.yaml +33 -0
  14. isotope_pattern_lib-1.0.0/isotope_pattern_lib/types/__init__.py +0 -0
  15. isotope_pattern_lib-1.0.0/isotope_pattern_lib/types/settings.py +52 -0
  16. isotope_pattern_lib-1.0.0/isotope_pattern_lib/types/types.py +109 -0
  17. isotope_pattern_lib-1.0.0/isotope_pattern_lib/utils/__init__.py +0 -0
  18. isotope_pattern_lib-1.0.0/isotope_pattern_lib/utils/utils.py +10 -0
  19. isotope_pattern_lib-1.0.0/pyproject.toml +44 -0
  20. isotope_pattern_lib-1.0.0/tests/__init__.py +0 -0
  21. isotope_pattern_lib-1.0.0/tests/data/integration/C2H5OH.yaml +20 -0
  22. isotope_pattern_lib-1.0.0/tests/data/integration/CH3CH2OH.yaml +20 -0
  23. isotope_pattern_lib-1.0.0/tests/data/integration/CO2.yaml +20 -0
  24. isotope_pattern_lib-1.0.0/tests/data/integration/H2NCH2COOH.yaml +56 -0
  25. isotope_pattern_lib-1.0.0/tests/data/integration/H2SO4.yaml +47 -0
  26. isotope_pattern_lib-1.0.0/tests/data/integration/config.yaml +36 -0
  27. isotope_pattern_lib-1.0.0/tests/data/unit/config_good.json +45 -0
  28. isotope_pattern_lib-1.0.0/tests/data/unit/config_good.yaml +24 -0
  29. isotope_pattern_lib-1.0.0/tests/data/unit/config_invalid_fields.yaml +24 -0
  30. isotope_pattern_lib-1.0.0/tests/data/unit/config_missing_fields.yaml +23 -0
  31. isotope_pattern_lib-1.0.0/tests/integration/__init__.py +0 -0
  32. isotope_pattern_lib-1.0.0/tests/integration/features/isotope_pattern_lib.feature +26 -0
  33. isotope_pattern_lib-1.0.0/tests/integration/step_defs/__init__.py +0 -0
  34. isotope_pattern_lib-1.0.0/tests/integration/step_defs/test_isotope_pattern_lib.py +64 -0
  35. isotope_pattern_lib-1.0.0/tests/unit/__init__.py +0 -0
  36. isotope_pattern_lib-1.0.0/tests/unit/conftest.py +41 -0
  37. isotope_pattern_lib-1.0.0/tests/unit/core/__init__.py +0 -0
  38. isotope_pattern_lib-1.0.0/tests/unit/core/test_isotope_pattern.py +104 -0
  39. isotope_pattern_lib-1.0.0/tests/unit/core/test_molecular_formula_parser.py +50 -0
  40. isotope_pattern_lib-1.0.0/tests/unit/test_api.py +65 -0
  41. isotope_pattern_lib-1.0.0/tests/unit/types/__init__.py +0 -0
  42. isotope_pattern_lib-1.0.0/tests/unit/types/test_element.py +87 -0
  43. isotope_pattern_lib-1.0.0/tests/unit/types/test_isotope.py +64 -0
  44. isotope_pattern_lib-1.0.0/tests/unit/types/test_isotope_formula.py +164 -0
  45. isotope_pattern_lib-1.0.0/tests/unit/types/test_molecular_formula.py +121 -0
  46. isotope_pattern_lib-1.0.0/tests/unit/types/test_settings.py +58 -0
  47. isotope_pattern_lib-1.0.0/tests/unit/utils/__init__.py +0 -0
  48. isotope_pattern_lib-1.0.0/tests/unit/utils/test_utils.py +33 -0
  49. isotope_pattern_lib-1.0.0/uv.lock +1709 -0
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # Computes the next release version from a PR title ([MAJOR]/[MINOR]/[PATCH])
3
+ # and the latest existing "X.Y.Z" git tag. Prints the version to stdout.
4
+ set -euo pipefail
5
+
6
+ title="${1:?usage: compute-next-version.sh <pr-title>}"
7
+
8
+ if echo "$title" | grep -qi '\[major\]'; then
9
+ bump="major"
10
+ elif echo "$title" | grep -qi '\[minor\]'; then
11
+ bump="minor"
12
+ elif echo "$title" | grep -qi '\[patch\]'; then
13
+ bump="patch"
14
+ else
15
+ echo "PR title must contain one of [MAJOR], [MINOR], [PATCH]: '${title}'" >&2
16
+ exit 1
17
+ fi
18
+
19
+ latest_tag=$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -t. -k1,1n -k2,2n -k3,3n | tail -1 || true)
20
+
21
+ if [ -z "$latest_tag" ]; then
22
+ if [ "$bump" != "major" ]; then
23
+ echo "No existing release tag found; a [${bump}] PR cannot be the first release, only [MAJOR] can" >&2
24
+ exit 1
25
+ fi
26
+ echo "1.0.0"
27
+ exit 0
28
+ fi
29
+
30
+ IFS='.' read -r major minor patch <<< "$latest_tag"
31
+
32
+ case "$bump" in
33
+ major)
34
+ echo "$((major + 1)).0.0"
35
+ ;;
36
+ minor)
37
+ echo "${major}.$((minor + 1)).0"
38
+ ;;
39
+ patch)
40
+ echo "${major}.${minor}.$((patch + 1))"
41
+ ;;
42
+ esac
@@ -0,0 +1,121 @@
1
+ name: ci-branch
2
+ env:
3
+ SONARCLOUD_TASK_URL: https://sonarcloud.io/api/ce/task
4
+ SONARCLOUD_PROJECT_STATUS_URL: https://sonarcloud.io/api/qualitygates/project_status
5
+ UV_PYTHON: "3.12"
6
+ on:
7
+ push:
8
+ branches:
9
+ - '**'
10
+ - '!master'
11
+ jobs:
12
+ test-matrix:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
18
+ steps:
19
+ - name: checkout
20
+ uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+ - name: install uv
24
+ uses: astral-sh/setup-uv@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ enable-cache: true
28
+ - name: running unit tests
29
+ run: |
30
+ uv run --python ${{ matrix.python-version }} --group test pytest tests/unit --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/unit/coverage-report.xml
31
+ - name: running integration tests
32
+ run: |
33
+ uv run --python ${{ matrix.python-version }} --group test pytest tests/integration --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/integration/coverage-report.xml
34
+ - name: saving artifacts
35
+ if: matrix.python-version == '3.12'
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: coverage-report
39
+ path: ./artifacts
40
+ # Fan-in gate: matrix jobs report as "test-matrix (3.9)" etc, not a single
41
+ # "tests" check, which breaks branch protection rules pinned to that name.
42
+ # This job restores a single, stably-named required check.
43
+ tests:
44
+ needs: test-matrix
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - name: all python version matrix legs passed
48
+ run: echo "All test-matrix legs passed"
49
+ code-analysis:
50
+ needs: tests
51
+ runs-on: ubuntu-latest
52
+ steps:
53
+ - name: checkout
54
+ uses: actions/checkout@v4
55
+ with:
56
+ fetch-depth: 0
57
+ - name: branch name extraction
58
+ id: branch_extraction
59
+ run: echo "##[set-output name=branch_name;]$(echo ${GITHUB_REF#refs/heads/})"
60
+ - name: loading artifacts
61
+ uses: actions/download-artifact@v4
62
+ with:
63
+ name: coverage-report
64
+ - name: SonarCloud scan
65
+ env:
66
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
67
+ SONAR_TOKEN: ${{secrets.SONARCLOUD_TOKEN}}
68
+ run: |
69
+ wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
70
+ unzip ./sonar-scanner-cli-8.1.0.6389-linux-x64.zip
71
+ rm ./sonar-scanner-cli-8.1.0.6389-linux-x64.zip
72
+ mv ./sonar-scanner-8.1.0.6389-linux-x64 /opt
73
+ mv /opt/sonar-scanner-8.1.0.6389-linux-x64 /opt/sonar-scanner
74
+ /opt/sonar-scanner/bin/sonar-scanner \
75
+ -Dsonar.organization=${{secrets.SONARCLOUD_ORGANIZATION}} \
76
+ -Dsonar.projectKey=${{secrets.SONARCLOUD_PROJECT_KEY}} \
77
+ -Dsonar.sources=isotope_pattern_lib \
78
+ -Dsonar.tests=tests \
79
+ -Dsonar.host.url=https://sonarcloud.io \
80
+ -Dsonar.python.coverage.reportPaths=test-report/**/coverage-report.xml \
81
+ -Dsonar.exclusions=test-report/** \
82
+ -Dsonar.branch.name=${{steps.branch_extraction.outputs.branch_name}}
83
+ - name: SonarCloud quality gate evaluation
84
+ env:
85
+ SONAR_TOKEN: ${{secrets.SONARCLOUD_TOKEN}}
86
+ run: |
87
+ taskId=$(awk -v FS="ceTaskId=" 'NF>1{print $2}' .scannerwork/report-task.txt)
88
+ echo "taskId=${taskId}"
89
+
90
+ status="PENDING"
91
+ for i in $(seq 1 30); do
92
+ taskResponse=$(curl -s -u "${SONAR_TOKEN}:" --location --request GET "${{env.SONARCLOUD_TASK_URL}}?id=${taskId}")
93
+ status=$(echo "$taskResponse" | jq -r '.task.status')
94
+ echo "CE task status: ${status}"
95
+ if [ "$status" = "SUCCESS" ] || [ "$status" = "FAILED" ] || [ "$status" = "CANCELED" ]; then
96
+ break
97
+ fi
98
+ sleep 5
99
+ done
100
+
101
+ if [ "$status" != "SUCCESS" ]; then
102
+ echo "SonarCloud analysis task did not complete successfully (status: ${status})"
103
+ echo "Full task response: ${taskResponse}"
104
+ exit 1
105
+ fi
106
+
107
+ analysisId=$(echo "$taskResponse" | jq -r '.task.analysisId')
108
+ echo "analysisId=${analysisId}"
109
+ qualityGateResponse=$(curl -s -u "${SONAR_TOKEN}:" --location --request GET "${{env.SONARCLOUD_PROJECT_STATUS_URL}}?analysisId=${analysisId}")
110
+ qualityGateStatus=$(echo "$qualityGateResponse" | jq -r '.projectStatus.status // empty')
111
+ echo "Quality gate status: ${qualityGateStatus:-<none>}"
112
+
113
+ if [ -z "$qualityGateStatus" ]; then
114
+ echo "Could not read quality gate status, full response: ${qualityGateResponse}"
115
+ exit 1
116
+ fi
117
+
118
+ if [ "$qualityGateStatus" = "ERROR" ]; then
119
+ echo "The quality gate was not passed"
120
+ exit 1
121
+ fi
@@ -0,0 +1,181 @@
1
+ name: ci-master
2
+ env:
3
+ SONARCLOUD_TASK_URL: https://sonarcloud.io/api/ce/task
4
+ SONARCLOUD_PROJECT_STATUS_URL: https://sonarcloud.io/api/qualitygates/project_status
5
+ UV_PYTHON: "3.12"
6
+ on:
7
+ push:
8
+ branches:
9
+ - master
10
+ jobs:
11
+ test-matrix:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
17
+ steps:
18
+ - name: checkout
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+ - name: install uv
23
+ uses: astral-sh/setup-uv@v5
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+ enable-cache: true
27
+ - name: running unit tests
28
+ run: |
29
+ uv run --python ${{ matrix.python-version }} --group test pytest tests/unit --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/unit/coverage-report.xml
30
+ - name: running integration tests
31
+ run: |
32
+ uv run --python ${{ matrix.python-version }} --group test pytest tests/integration --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/integration/coverage-report.xml
33
+ - name: saving artifacts
34
+ if: matrix.python-version == '3.12'
35
+ uses: actions/upload-artifact@v4
36
+ with:
37
+ name: coverage-report
38
+ path: ./artifacts
39
+ # Fan-in gate: matrix jobs report as "test-matrix (3.9)" etc, not a single
40
+ # "tests" check, which breaks branch protection rules pinned to that name.
41
+ # This job restores a single, stably-named required check.
42
+ tests:
43
+ needs: test-matrix
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - name: all python version matrix legs passed
47
+ run: echo "All test-matrix legs passed"
48
+ code-analysis:
49
+ needs: tests
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - name: checkout
53
+ uses: actions/checkout@v4
54
+ with:
55
+ fetch-depth: 0
56
+ - name: loading artifacts
57
+ uses: actions/download-artifact@v4
58
+ with:
59
+ name: coverage-report
60
+ - name: SonarCloud scan
61
+ env:
62
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
63
+ SONAR_TOKEN: ${{secrets.SONARCLOUD_TOKEN}}
64
+ run: |
65
+ wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
66
+ unzip ./sonar-scanner-cli-8.1.0.6389-linux-x64.zip
67
+ rm ./sonar-scanner-cli-8.1.0.6389-linux-x64.zip
68
+ mv ./sonar-scanner-8.1.0.6389-linux-x64 /opt
69
+ mv /opt/sonar-scanner-8.1.0.6389-linux-x64 /opt/sonar-scanner
70
+ /opt/sonar-scanner/bin/sonar-scanner \
71
+ -Dsonar.organization=${{secrets.SONARCLOUD_ORGANIZATION}} \
72
+ -Dsonar.projectKey=${{secrets.SONARCLOUD_PROJECT_KEY}} \
73
+ -Dsonar.sources=isotope_pattern_lib \
74
+ -Dsonar.tests=tests \
75
+ -Dsonar.host.url=https://sonarcloud.io \
76
+ -Dsonar.python.coverage.reportPaths=test-report/**/coverage-report.xml \
77
+ -Dsonar.exclusions=test-report/** \
78
+ -Dsonar.branch.name=master
79
+ - name: SonarCloud quality gate evaluation
80
+ env:
81
+ SONAR_TOKEN: ${{secrets.SONARCLOUD_TOKEN}}
82
+ run: |
83
+ taskId=$(awk -v FS="ceTaskId=" 'NF>1{print $2}' .scannerwork/report-task.txt)
84
+ echo "taskId=${taskId}"
85
+
86
+ status="PENDING"
87
+ for i in $(seq 1 30); do
88
+ taskResponse=$(curl -s -u "${SONAR_TOKEN}:" --location --request GET "${{env.SONARCLOUD_TASK_URL}}?id=${taskId}")
89
+ status=$(echo "$taskResponse" | jq -r '.task.status')
90
+ echo "CE task status: ${status}"
91
+ if [ "$status" = "SUCCESS" ] || [ "$status" = "FAILED" ] || [ "$status" = "CANCELED" ]; then
92
+ break
93
+ fi
94
+ sleep 5
95
+ done
96
+
97
+ if [ "$status" != "SUCCESS" ]; then
98
+ echo "SonarCloud analysis task did not complete successfully (status: ${status})"
99
+ echo "Full task response: ${taskResponse}"
100
+ exit 1
101
+ fi
102
+
103
+ analysisId=$(echo "$taskResponse" | jq -r '.task.analysisId')
104
+ echo "analysisId=${analysisId}"
105
+ qualityGateResponse=$(curl -s -u "${SONAR_TOKEN}:" --location --request GET "${{env.SONARCLOUD_PROJECT_STATUS_URL}}?analysisId=${analysisId}")
106
+ qualityGateStatus=$(echo "$qualityGateResponse" | jq -r '.projectStatus.status // empty')
107
+ echo "Quality gate status: ${qualityGateStatus:-<none>}"
108
+
109
+ if [ -z "$qualityGateStatus" ]; then
110
+ echo "Could not read quality gate status, full response: ${qualityGateResponse}"
111
+ exit 1
112
+ fi
113
+
114
+ if [ "$qualityGateStatus" = "ERROR" ]; then
115
+ echo "The quality gate was not passed"
116
+ exit 1
117
+ fi
118
+ tag-release:
119
+ needs: [tests, code-analysis]
120
+ runs-on: ubuntu-latest
121
+ permissions:
122
+ contents: write
123
+ outputs:
124
+ version: ${{ steps.tag.outputs.version }}
125
+ steps:
126
+ - name: checkout
127
+ uses: actions/checkout@v4
128
+ with:
129
+ fetch-depth: 0
130
+ - name: find merged PR title
131
+ id: pr
132
+ env:
133
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
134
+ run: |
135
+ title=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" --jq '.[0].title // empty')
136
+ if [ -z "$title" ]; then
137
+ echo "No pull request associated with this push; skipping release tagging" >&2
138
+ exit 1
139
+ fi
140
+ echo "title=${title}" | tee -a "$GITHUB_OUTPUT"
141
+ - name: compute next version
142
+ id: version
143
+ env:
144
+ PR_TITLE: ${{ steps.pr.outputs.title }}
145
+ run: |
146
+ version=$(.github/scripts/compute-next-version.sh "$PR_TITLE")
147
+ echo "version=${version}" | tee -a "$GITHUB_OUTPUT"
148
+ - name: create and push release tag
149
+ id: tag
150
+ env:
151
+ VERSION: ${{ steps.version.outputs.version }}
152
+ run: |
153
+ git config user.name "github-actions[bot]"
154
+ git config user.email "github-actions[bot]@users.noreply.github.com"
155
+ git tag -a "${VERSION}" -m "Release ${VERSION}"
156
+ git push origin "${VERSION}"
157
+ echo "version=${VERSION}" | tee -a "$GITHUB_OUTPUT"
158
+ publish:
159
+ needs: tag-release
160
+ runs-on: ubuntu-latest
161
+ steps:
162
+ - name: checkout
163
+ uses: actions/checkout@v4
164
+ with:
165
+ ref: ${{ needs.tag-release.outputs.version }}
166
+ - name: install uv
167
+ uses: astral-sh/setup-uv@v5
168
+ with:
169
+ python-version: ${{ env.UV_PYTHON }}
170
+ enable-cache: true
171
+ - name: set release version
172
+ env:
173
+ VERSION: ${{ needs.tag-release.outputs.version }}
174
+ run: |
175
+ sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml
176
+ - name: build package
177
+ run: uv build
178
+ - name: publish to PyPI
179
+ env:
180
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
181
+ run: uv publish dist/*
@@ -0,0 +1,209 @@
1
+ name: ci-pull-request
2
+ env:
3
+ SONARCLOUD_TASK_URL: https://sonarcloud.io/api/ce/task
4
+ SONARCLOUD_PROJECT_STATUS_URL: https://sonarcloud.io/api/qualitygates/project_status
5
+ UV_PYTHON: "3.12"
6
+ on:
7
+ pull_request:
8
+ branches:
9
+ - master
10
+ types: [opened, edited, synchronize, reopened]
11
+ jobs:
12
+ validate-pr-title:
13
+ runs-on: ubuntu-latest
14
+ outputs:
15
+ next_version: ${{ steps.compute.outputs.version }}
16
+ steps:
17
+ - name: checkout
18
+ uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+ - name: compute next version from PR title
22
+ id: compute
23
+ env:
24
+ PR_TITLE: ${{ github.event.pull_request.title }}
25
+ run: |
26
+ version=$(.github/scripts/compute-next-version.sh "$PR_TITLE")
27
+ echo "version=${version}" | tee -a "$GITHUB_OUTPUT"
28
+ test-matrix:
29
+ needs: validate-pr-title
30
+ runs-on: ubuntu-latest
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
35
+ steps:
36
+ - name: checkout
37
+ uses: actions/checkout@v4
38
+ with:
39
+ fetch-depth: 0
40
+ - name: install uv
41
+ uses: astral-sh/setup-uv@v5
42
+ with:
43
+ python-version: ${{ matrix.python-version }}
44
+ enable-cache: true
45
+ - name: running unit tests
46
+ run: |
47
+ uv run --python ${{ matrix.python-version }} --group test pytest tests/unit --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/unit/coverage-report.xml
48
+ - name: running integration tests
49
+ run: |
50
+ uv run --python ${{ matrix.python-version }} --group test pytest tests/integration --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/integration/coverage-report.xml
51
+ - name: saving artifacts
52
+ if: matrix.python-version == '3.12'
53
+ uses: actions/upload-artifact@v4
54
+ with:
55
+ name: coverage-report
56
+ path: ./artifacts
57
+ # Fan-in gate: matrix jobs report as "test-matrix (3.9)" etc, not a single
58
+ # "tests" check, which breaks branch protection rules pinned to that name.
59
+ # This job restores a single, stably-named required check.
60
+ tests:
61
+ needs: test-matrix
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - name: all python version matrix legs passed
65
+ run: echo "All test-matrix legs passed"
66
+ code-analysis:
67
+ needs: tests
68
+ runs-on: ubuntu-latest
69
+ steps:
70
+ - name: checkout
71
+ uses: actions/checkout@v4
72
+ with:
73
+ fetch-depth: 0
74
+ - name: loading artifacts
75
+ uses: actions/download-artifact@v4
76
+ with:
77
+ name: coverage-report
78
+ - name: SonarCloud scan
79
+ env:
80
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
81
+ SONAR_TOKEN: ${{secrets.SONARCLOUD_TOKEN}}
82
+ run: |
83
+ wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
84
+ unzip ./sonar-scanner-cli-8.1.0.6389-linux-x64.zip
85
+ rm ./sonar-scanner-cli-8.1.0.6389-linux-x64.zip
86
+ mv ./sonar-scanner-8.1.0.6389-linux-x64 /opt
87
+ mv /opt/sonar-scanner-8.1.0.6389-linux-x64 /opt/sonar-scanner
88
+ /opt/sonar-scanner/bin/sonar-scanner \
89
+ -Dsonar.organization=${{secrets.SONARCLOUD_ORGANIZATION}} \
90
+ -Dsonar.projectKey=${{secrets.SONARCLOUD_PROJECT_KEY}} \
91
+ -Dsonar.sources=isotope_pattern_lib \
92
+ -Dsonar.tests=tests \
93
+ -Dsonar.host.url=https://sonarcloud.io \
94
+ -Dsonar.python.coverage.reportPaths=test-report/**/coverage-report.xml \
95
+ -Dsonar.exclusions=test-report/** \
96
+ -Dsonar.pullrequest.key=${{github.event.pull_request.number}}
97
+ - name: SonarCloud quality gate evaluation
98
+ env:
99
+ SONAR_TOKEN: ${{secrets.SONARCLOUD_TOKEN}}
100
+ run: |
101
+ taskId=$(awk -v FS="ceTaskId=" 'NF>1{print $2}' .scannerwork/report-task.txt)
102
+ echo "taskId=${taskId}"
103
+
104
+ status="PENDING"
105
+ for i in $(seq 1 30); do
106
+ taskResponse=$(curl -s -u "${SONAR_TOKEN}:" --location --request GET "${{env.SONARCLOUD_TASK_URL}}?id=${taskId}")
107
+ status=$(echo "$taskResponse" | jq -r '.task.status')
108
+ echo "CE task status: ${status}"
109
+ if [ "$status" = "SUCCESS" ] || [ "$status" = "FAILED" ] || [ "$status" = "CANCELED" ]; then
110
+ break
111
+ fi
112
+ sleep 5
113
+ done
114
+
115
+ if [ "$status" != "SUCCESS" ]; then
116
+ echo "SonarCloud analysis task did not complete successfully (status: ${status})"
117
+ echo "Full task response: ${taskResponse}"
118
+ exit 1
119
+ fi
120
+
121
+ analysisId=$(echo "$taskResponse" | jq -r '.task.analysisId')
122
+ echo "analysisId=${analysisId}"
123
+ qualityGateResponse=$(curl -s -u "${SONAR_TOKEN}:" --location --request GET "${{env.SONARCLOUD_PROJECT_STATUS_URL}}?analysisId=${analysisId}")
124
+ qualityGateStatus=$(echo "$qualityGateResponse" | jq -r '.projectStatus.status // empty')
125
+ echo "Quality gate status: ${qualityGateStatus:-<none>}"
126
+
127
+ if [ -z "$qualityGateStatus" ]; then
128
+ echo "Could not read quality gate status, full response: ${qualityGateResponse}"
129
+ exit 1
130
+ fi
131
+
132
+ if [ "$qualityGateStatus" = "ERROR" ]; then
133
+ echo "The quality gate was not passed"
134
+ exit 1
135
+ fi
136
+ e2e-publish:
137
+ needs: [tests, validate-pr-title]
138
+ runs-on: ubuntu-latest
139
+ outputs:
140
+ version: ${{ steps.version.outputs.version }}
141
+ steps:
142
+ - name: checkout
143
+ uses: actions/checkout@v4
144
+ - name: install uv
145
+ uses: astral-sh/setup-uv@v5
146
+ with:
147
+ python-version: ${{ env.UV_PYTHON }}
148
+ enable-cache: true
149
+ - name: compute unique dev version
150
+ id: version
151
+ env:
152
+ NEXT_VERSION: ${{ needs.validate-pr-title.outputs.next_version }}
153
+ run: |
154
+ dev_version="${NEXT_VERSION}.dev${{ github.run_id }}${{ github.run_attempt }}"
155
+ echo "version=${dev_version}" | tee -a "$GITHUB_OUTPUT"
156
+ sed -i "s/^version = \".*\"/version = \"${dev_version}\"/" pyproject.toml
157
+ - name: build package
158
+ run: uv build
159
+ - name: publish to TestPyPI
160
+ env:
161
+ UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_TOKEN }}
162
+ run: uv publish --publish-url https://test.pypi.org/legacy/ dist/*
163
+ e2e-verify:
164
+ needs: e2e-publish
165
+ runs-on: ubuntu-latest
166
+ steps:
167
+ # Sparse-checkout only `tests/`: `isotope_pattern_lib` must NOT be present
168
+ # in the working tree here, otherwise `python -m pytest` would resolve
169
+ # imports to the local source instead of the package installed from
170
+ # TestPyPI below, silently defeating this job's purpose.
171
+ - name: checkout tests only
172
+ uses: actions/checkout@v4
173
+ with:
174
+ sparse-checkout: |
175
+ tests
176
+ sparse-checkout-cone-mode: true
177
+ - name: install uv
178
+ uses: astral-sh/setup-uv@v5
179
+ with:
180
+ python-version: ${{ env.UV_PYTHON }}
181
+ enable-cache: true
182
+ - name: create e2e virtual environment
183
+ run: uv venv .e2e-venv
184
+ - name: install package from TestPyPI
185
+ env:
186
+ VERSION: ${{ needs.e2e-publish.outputs.version }}
187
+ run: |
188
+ for attempt in $(seq 1 5); do
189
+ if uv pip install --python .e2e-venv \
190
+ --index-url https://test.pypi.org/simple/ \
191
+ --extra-index-url https://pypi.org/simple/ \
192
+ "isotope-pattern-lib==${VERSION}"; then
193
+ exit 0
194
+ fi
195
+ echo "package not yet available on TestPyPI, retrying in 15s (attempt ${attempt}/5)"
196
+ sleep 15
197
+ done
198
+ echo "failed to install isotope-pattern-lib==${VERSION} from TestPyPI"
199
+ exit 1
200
+ - name: install e2e test dependencies
201
+ run: uv pip install --python .e2e-venv pytest pytest-bdd pytest-cov pyyaml
202
+ - name: running e2e tests against published artifact
203
+ run: |
204
+ .e2e-venv/bin/python -m pytest tests/integration --cov=isotope_pattern_lib --cov-report=xml:./artifacts/test-report/e2e/coverage-report.xml
205
+ - name: saving e2e artifacts
206
+ uses: actions/upload-artifact@v4
207
+ with:
208
+ name: e2e-coverage-report
209
+ path: ./artifacts
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ artifacts/
3
+ *.egg-info/
4
+ .idea/
5
+ .DS_Store
6
+ .coverage
7
+ .scannerwork/
8
+ .venv/
9
+ dist/