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.
- isotope_pattern_lib-1.0.0/.github/scripts/compute-next-version.sh +42 -0
- isotope_pattern_lib-1.0.0/.github/workflows/ci-branch.yaml +121 -0
- isotope_pattern_lib-1.0.0/.github/workflows/ci-master.yaml +181 -0
- isotope_pattern_lib-1.0.0/.github/workflows/ci-pull-request.yaml +209 -0
- isotope_pattern_lib-1.0.0/.gitignore +9 -0
- isotope_pattern_lib-1.0.0/PKG-INFO +161 -0
- isotope_pattern_lib-1.0.0/README.md +143 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/api.py +33 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/core/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/core/formula_parser.py +30 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/core/isotope_pattern.py +51 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/resources/config.yaml +33 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/types/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/types/settings.py +52 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/types/types.py +109 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/utils/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/isotope_pattern_lib/utils/utils.py +10 -0
- isotope_pattern_lib-1.0.0/pyproject.toml +44 -0
- isotope_pattern_lib-1.0.0/tests/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/data/integration/C2H5OH.yaml +20 -0
- isotope_pattern_lib-1.0.0/tests/data/integration/CH3CH2OH.yaml +20 -0
- isotope_pattern_lib-1.0.0/tests/data/integration/CO2.yaml +20 -0
- isotope_pattern_lib-1.0.0/tests/data/integration/H2NCH2COOH.yaml +56 -0
- isotope_pattern_lib-1.0.0/tests/data/integration/H2SO4.yaml +47 -0
- isotope_pattern_lib-1.0.0/tests/data/integration/config.yaml +36 -0
- isotope_pattern_lib-1.0.0/tests/data/unit/config_good.json +45 -0
- isotope_pattern_lib-1.0.0/tests/data/unit/config_good.yaml +24 -0
- isotope_pattern_lib-1.0.0/tests/data/unit/config_invalid_fields.yaml +24 -0
- isotope_pattern_lib-1.0.0/tests/data/unit/config_missing_fields.yaml +23 -0
- isotope_pattern_lib-1.0.0/tests/integration/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/integration/features/isotope_pattern_lib.feature +26 -0
- isotope_pattern_lib-1.0.0/tests/integration/step_defs/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/integration/step_defs/test_isotope_pattern_lib.py +64 -0
- isotope_pattern_lib-1.0.0/tests/unit/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/unit/conftest.py +41 -0
- isotope_pattern_lib-1.0.0/tests/unit/core/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/unit/core/test_isotope_pattern.py +104 -0
- isotope_pattern_lib-1.0.0/tests/unit/core/test_molecular_formula_parser.py +50 -0
- isotope_pattern_lib-1.0.0/tests/unit/test_api.py +65 -0
- isotope_pattern_lib-1.0.0/tests/unit/types/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/unit/types/test_element.py +87 -0
- isotope_pattern_lib-1.0.0/tests/unit/types/test_isotope.py +64 -0
- isotope_pattern_lib-1.0.0/tests/unit/types/test_isotope_formula.py +164 -0
- isotope_pattern_lib-1.0.0/tests/unit/types/test_molecular_formula.py +121 -0
- isotope_pattern_lib-1.0.0/tests/unit/types/test_settings.py +58 -0
- isotope_pattern_lib-1.0.0/tests/unit/utils/__init__.py +0 -0
- isotope_pattern_lib-1.0.0/tests/unit/utils/test_utils.py +33 -0
- 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
|