pycrestron-cip 0.1.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.
- pycrestron_cip-0.1.0/.github/workflows/auto-pr.yml +155 -0
- pycrestron_cip-0.1.0/.github/workflows/ci.yml +54 -0
- pycrestron_cip-0.1.0/.github/workflows/release.yml +189 -0
- pycrestron_cip-0.1.0/.github/workflows/version-label.yml +50 -0
- pycrestron_cip-0.1.0/.gitignore +9 -0
- pycrestron_cip-0.1.0/CODEOWNERS +1 -0
- pycrestron_cip-0.1.0/CONTRIBUTING.md +84 -0
- pycrestron_cip-0.1.0/LICENSE +21 -0
- pycrestron_cip-0.1.0/PKG-INFO +203 -0
- pycrestron_cip-0.1.0/README.md +169 -0
- pycrestron_cip-0.1.0/docs/protocol.md +87 -0
- pycrestron_cip-0.1.0/pyproject.toml +59 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/__init__.py +29 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/__version__.py +1 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/client.py +503 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/exceptions.py +27 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/probe.py +95 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/protocol.py +379 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/py.typed +0 -0
- pycrestron_cip-0.1.0/src/pycrestron_cip/testing.py +211 -0
- pycrestron_cip-0.1.0/tests/test_client.py +240 -0
- pycrestron_cip-0.1.0/tests/test_probe.py +26 -0
- pycrestron_cip-0.1.0/tests/test_protocol.py +158 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
name: Auto PR to main
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [development]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: auto-pr
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
create-pr:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
# Skip if the push is the release workflow syncing main back into
|
|
15
|
+
# development (prevents loop; message comes from `git merge origin/main`)
|
|
16
|
+
if: ${{ !contains(github.event.head_commit.message, 'Merge remote-tracking branch ''origin/main''') }}
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
22
|
+
|
|
23
|
+
- name: Check for meaningful diff
|
|
24
|
+
id: diff
|
|
25
|
+
env:
|
|
26
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
run: |
|
|
28
|
+
# Only create PR if development is actually ahead of main
|
|
29
|
+
if git diff origin/main...origin/development --quiet; then
|
|
30
|
+
echo "has_diff=false" >> "$GITHUB_OUTPUT"
|
|
31
|
+
else
|
|
32
|
+
echo "has_diff=true" >> "$GITHUB_OUTPUT"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
- name: Build changelog
|
|
36
|
+
if: steps.diff.outputs.has_diff == 'true'
|
|
37
|
+
id: changelog
|
|
38
|
+
env:
|
|
39
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
40
|
+
run: |
|
|
41
|
+
pr_numbers=$(git log origin/main..origin/development --first-parent --format='%s' \
|
|
42
|
+
| sed -nE 's/^Merge pull request #([0-9]+).*/\1/p; s/.* \(#([0-9]+)\)$/\1/p' \
|
|
43
|
+
| awk '!seen[$0]++' \
|
|
44
|
+
| head -50)
|
|
45
|
+
|
|
46
|
+
pr_lines=""
|
|
47
|
+
if [ -n "$pr_numbers" ]; then
|
|
48
|
+
while IFS= read -r number; do
|
|
49
|
+
[ -n "$number" ] || continue
|
|
50
|
+
|
|
51
|
+
line=$(gh pr view "$number" --json number,title,author \
|
|
52
|
+
--jq '"- #\(.number) \(.title) (@\(.author.login))"' 2>/dev/null || true)
|
|
53
|
+
|
|
54
|
+
if [ -n "$line" ]; then
|
|
55
|
+
pr_lines="${pr_lines}${line}\n"
|
|
56
|
+
fi
|
|
57
|
+
done < <(printf '%s\n' "$pr_numbers")
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
commit_lines=$(git log origin/main..origin/development --oneline --no-merges | head -50)
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
echo 'body<<CHANGELOG_EOF'
|
|
64
|
+
echo '## Changes since last release'
|
|
65
|
+
echo ''
|
|
66
|
+
|
|
67
|
+
echo '### Merged pull requests'
|
|
68
|
+
if [ -n "$pr_lines" ]; then
|
|
69
|
+
printf '%b' "$pr_lines"
|
|
70
|
+
else
|
|
71
|
+
echo '- No pull requests detected from the unreleased commit range.'
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo ''
|
|
75
|
+
echo '### Commits'
|
|
76
|
+
if [ -n "$commit_lines" ]; then
|
|
77
|
+
printf '%s\n' "$commit_lines"
|
|
78
|
+
else
|
|
79
|
+
echo '- No non-merge commits since main.'
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
echo ''
|
|
83
|
+
echo '---'
|
|
84
|
+
echo "*Auto-generated from the development branch.*"
|
|
85
|
+
echo 'CHANGELOG_EOF'
|
|
86
|
+
} >> "$GITHUB_OUTPUT"
|
|
87
|
+
|
|
88
|
+
- name: Determine release label
|
|
89
|
+
if: steps.diff.outputs.has_diff == 'true'
|
|
90
|
+
id: label
|
|
91
|
+
run: |
|
|
92
|
+
# Derive the version bump from conventional-commit messages in the
|
|
93
|
+
# unreleased range: breaking -> major, feat -> minor, else patch.
|
|
94
|
+
subjects=$(git log origin/main..origin/development --no-merges --format='%s')
|
|
95
|
+
bodies=$(git log origin/main..origin/development --no-merges --format='%b')
|
|
96
|
+
|
|
97
|
+
label="patch"
|
|
98
|
+
if printf '%s\n' "$subjects" | grep -qE '^feat(\([^)]*\))?:'; then
|
|
99
|
+
label="minor"
|
|
100
|
+
fi
|
|
101
|
+
if printf '%s\n' "$subjects" | grep -qE '^[a-z]+(\([^)]*\))?!:' ||
|
|
102
|
+
printf '%s\n' "$bodies" | grep -q 'BREAKING CHANGE'; then
|
|
103
|
+
label="major"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
echo "Suggested release label: ${label}"
|
|
107
|
+
echo "label=${label}" >> "$GITHUB_OUTPUT"
|
|
108
|
+
|
|
109
|
+
- name: Create or update PR development → main
|
|
110
|
+
if: steps.diff.outputs.has_diff == 'true'
|
|
111
|
+
env:
|
|
112
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
113
|
+
PR_BODY: ${{ steps.changelog.outputs.body }}
|
|
114
|
+
LABEL: ${{ steps.label.outputs.label }}
|
|
115
|
+
run: |
|
|
116
|
+
rank() {
|
|
117
|
+
case "$1" in
|
|
118
|
+
major) echo 3 ;;
|
|
119
|
+
minor) echo 2 ;;
|
|
120
|
+
patch) echo 1 ;;
|
|
121
|
+
*) echo 0 ;;
|
|
122
|
+
esac
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Check if an open PR from development → main already exists
|
|
126
|
+
existing_pr=$(gh pr list --base main --head development --state open --json number --jq '.[0].number')
|
|
127
|
+
|
|
128
|
+
if [ -n "$existing_pr" ]; then
|
|
129
|
+
echo "Updating PR #$existing_pr body with latest changelog."
|
|
130
|
+
gh pr edit "$existing_pr" --body "$PR_BODY"
|
|
131
|
+
|
|
132
|
+
# Apply the suggested label, but never override a human choice:
|
|
133
|
+
# leave no-release alone and never downgrade a version label.
|
|
134
|
+
labels=$(gh pr view "$existing_pr" --json labels --jq '.labels[].name')
|
|
135
|
+
if printf '%s\n' "$labels" | grep -qx 'no-release'; then
|
|
136
|
+
echo "PR is labeled no-release; leaving labels untouched."
|
|
137
|
+
exit 0
|
|
138
|
+
fi
|
|
139
|
+
current=$(printf '%s\n' "$labels" | grep -m 1 -xE 'major|minor|patch' | cat)
|
|
140
|
+
if [ "$(rank "$current")" -lt "$(rank "$LABEL")" ]; then
|
|
141
|
+
if [ -n "$current" ]; then
|
|
142
|
+
gh pr edit "$existing_pr" --remove-label "$current" --add-label "$LABEL"
|
|
143
|
+
else
|
|
144
|
+
gh pr edit "$existing_pr" --add-label "$LABEL"
|
|
145
|
+
fi
|
|
146
|
+
echo "Set release label: $LABEL"
|
|
147
|
+
fi
|
|
148
|
+
else
|
|
149
|
+
gh pr create \
|
|
150
|
+
--base main \
|
|
151
|
+
--head development \
|
|
152
|
+
--title "Merge development into main" \
|
|
153
|
+
--body "$PR_BODY" \
|
|
154
|
+
--label "$LABEL"
|
|
155
|
+
fi
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, development]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main, development]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
workflow-lint:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- name: Lint GitHub Actions workflows
|
|
19
|
+
uses: docker://rhysd/actionlint:latest
|
|
20
|
+
|
|
21
|
+
lint:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
- uses: actions/setup-python@v7
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.14"
|
|
28
|
+
- run: pip install ruff
|
|
29
|
+
- run: ruff check .
|
|
30
|
+
- run: ruff format --check .
|
|
31
|
+
|
|
32
|
+
test:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
strategy:
|
|
35
|
+
matrix:
|
|
36
|
+
python-version: ["3.13", "3.14"]
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v7
|
|
39
|
+
- uses: actions/setup-python@v7
|
|
40
|
+
with:
|
|
41
|
+
python-version: ${{ matrix.python-version }}
|
|
42
|
+
- run: pip install -e ".[dev]"
|
|
43
|
+
- run: pytest --cov=pycrestron_cip --cov-report=term-missing
|
|
44
|
+
|
|
45
|
+
build:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v7
|
|
49
|
+
- uses: actions/setup-python@v7
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.14"
|
|
52
|
+
- run: pip install build twine
|
|
53
|
+
- run: python -m build
|
|
54
|
+
- run: twine check dist/*
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: release
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
bump-version:
|
|
18
|
+
if: >
|
|
19
|
+
github.event.pull_request.merged == true
|
|
20
|
+
&& github.event.pull_request.head.ref == 'development'
|
|
21
|
+
&& !contains(github.event.pull_request.labels.*.name, 'no-release')
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
outputs:
|
|
24
|
+
version: ${{ steps.version.outputs.version }}
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v7
|
|
27
|
+
with:
|
|
28
|
+
ref: main
|
|
29
|
+
fetch-depth: 0
|
|
30
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
31
|
+
|
|
32
|
+
- uses: actions/setup-python@v7
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.14"
|
|
35
|
+
|
|
36
|
+
- name: Validate release label
|
|
37
|
+
id: bump
|
|
38
|
+
env:
|
|
39
|
+
LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }}
|
|
40
|
+
run: |
|
|
41
|
+
count=$(echo "$LABELS" | jq '[.[] | select(. == "major" or . == "minor" or . == "patch")] | length')
|
|
42
|
+
|
|
43
|
+
if [ "$count" -ne 1 ]; then
|
|
44
|
+
echo "::error::Merged PRs to main must have exactly one version label: major, minor, or patch."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
part=$(echo "$LABELS" | jq -r '[.[] | select(. == "major" or . == "minor" or . == "patch")][0]')
|
|
49
|
+
echo "part=${part}" >> "$GITHUB_OUTPUT"
|
|
50
|
+
|
|
51
|
+
- name: Detect existing release bump
|
|
52
|
+
id: state
|
|
53
|
+
env:
|
|
54
|
+
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
|
55
|
+
run: |
|
|
56
|
+
head_message=$(git log -1 --format=%s main)
|
|
57
|
+
if printf '%s\n' "$head_message" | grep -qE '^Bump version to '; then
|
|
58
|
+
version=$(python3 -c "exec(open('src/pycrestron_cip/__version__.py').read()); print(__version__)")
|
|
59
|
+
echo "released=true" >> "$GITHUB_OUTPUT"
|
|
60
|
+
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
61
|
+
echo "Main already points at release version ${version}."
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
first_descendant=$(git rev-list --ancestry-path --reverse "${MERGE_SHA}..main" | head -n 1)
|
|
66
|
+
if [ -n "$first_descendant" ]; then
|
|
67
|
+
first_message=$(git log -1 --format=%s "$first_descendant")
|
|
68
|
+
if printf '%s\n' "$first_message" | grep -qE '^Bump version to '; then
|
|
69
|
+
echo "released=true" >> "$GITHUB_OUTPUT"
|
|
70
|
+
echo "version=${first_message#Bump version to }" >> "$GITHUB_OUTPUT"
|
|
71
|
+
echo "Found existing release bump for this merge."
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo "released=false" >> "$GITHUB_OUTPUT"
|
|
77
|
+
|
|
78
|
+
- name: Determine release version
|
|
79
|
+
id: version
|
|
80
|
+
run: |
|
|
81
|
+
if [ "${{ steps.state.outputs.released }}" = "true" ]; then
|
|
82
|
+
echo "version=${{ steps.state.outputs.version }}" >> "$GITHUB_OUTPUT"
|
|
83
|
+
echo "Reusing version ${{ steps.state.outputs.version }}"
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
FILE="src/pycrestron_cip/__version__.py"
|
|
88
|
+
CURRENT=$(python3 -c "exec(open('$FILE').read()); print(__version__)")
|
|
89
|
+
IFS='.' read -r major minor patch <<< "$CURRENT"
|
|
90
|
+
|
|
91
|
+
PART="${{ steps.bump.outputs.part }}"
|
|
92
|
+
if [ "$PART" = "major" ]; then
|
|
93
|
+
major=$((major + 1)); minor=0; patch=0
|
|
94
|
+
elif [ "$PART" = "minor" ]; then
|
|
95
|
+
minor=$((minor + 1)); patch=0
|
|
96
|
+
else
|
|
97
|
+
patch=$((patch + 1))
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
NEW="${major}.${minor}.${patch}"
|
|
101
|
+
echo "__version__ = \"${NEW}\"" > "$FILE"
|
|
102
|
+
echo "version=${NEW}" >> "$GITHUB_OUTPUT"
|
|
103
|
+
echo "Bumped $CURRENT -> $NEW ($PART)"
|
|
104
|
+
|
|
105
|
+
- name: Commit and tag
|
|
106
|
+
if: steps.state.outputs.released != 'true'
|
|
107
|
+
run: |
|
|
108
|
+
git config user.name "github-actions[bot]"
|
|
109
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
110
|
+
git add src/pycrestron_cip/__version__.py
|
|
111
|
+
git commit -m "Bump version to ${{ steps.version.outputs.version }}"
|
|
112
|
+
git tag "v${{ steps.version.outputs.version }}"
|
|
113
|
+
git push origin main "refs/tags/v${{ steps.version.outputs.version }}"
|
|
114
|
+
|
|
115
|
+
- name: Create GitHub release
|
|
116
|
+
env:
|
|
117
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
118
|
+
run: |
|
|
119
|
+
if gh release view "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
|
|
120
|
+
echo "GitHub release already exists for v${{ steps.version.outputs.version }}."
|
|
121
|
+
else
|
|
122
|
+
gh release create "v${{ steps.version.outputs.version }}" \
|
|
123
|
+
--title "v${{ steps.version.outputs.version }}" \
|
|
124
|
+
--generate-notes
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
sync-development:
|
|
128
|
+
needs: bump-version
|
|
129
|
+
# Run for no-release merges too (bump-version is skipped then), so the
|
|
130
|
+
# merge commit on main always flows back into development.
|
|
131
|
+
if: >
|
|
132
|
+
always()
|
|
133
|
+
&& github.event.pull_request.merged == true
|
|
134
|
+
&& github.event.pull_request.head.ref == 'development'
|
|
135
|
+
&& (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped')
|
|
136
|
+
runs-on: ubuntu-latest
|
|
137
|
+
steps:
|
|
138
|
+
- uses: actions/checkout@v7
|
|
139
|
+
with:
|
|
140
|
+
ref: development
|
|
141
|
+
fetch-depth: 0
|
|
142
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
143
|
+
|
|
144
|
+
- name: Merge main into development
|
|
145
|
+
run: |
|
|
146
|
+
git config user.name "github-actions[bot]"
|
|
147
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
148
|
+
# Retry so a racing push to development can't leave the version
|
|
149
|
+
# bump stranded on main.
|
|
150
|
+
for attempt in 1 2 3; do
|
|
151
|
+
git fetch origin main development
|
|
152
|
+
git reset --hard origin/development
|
|
153
|
+
git merge origin/main --no-edit
|
|
154
|
+
if git push origin development; then
|
|
155
|
+
exit 0
|
|
156
|
+
fi
|
|
157
|
+
echo "Push rejected (attempt ${attempt}); retrying."
|
|
158
|
+
sleep 5
|
|
159
|
+
done
|
|
160
|
+
echo "::error::Failed to sync development with main after 3 attempts."
|
|
161
|
+
exit 1
|
|
162
|
+
|
|
163
|
+
publish:
|
|
164
|
+
needs: bump-version
|
|
165
|
+
if: needs.bump-version.result == 'success'
|
|
166
|
+
runs-on: ubuntu-latest
|
|
167
|
+
environment:
|
|
168
|
+
name: pypi
|
|
169
|
+
url: https://pypi.org/p/pycrestron-cip
|
|
170
|
+
permissions:
|
|
171
|
+
id-token: write
|
|
172
|
+
steps:
|
|
173
|
+
- uses: actions/checkout@v7
|
|
174
|
+
with:
|
|
175
|
+
ref: v${{ needs.bump-version.outputs.version }}
|
|
176
|
+
|
|
177
|
+
- uses: actions/setup-python@v7
|
|
178
|
+
with:
|
|
179
|
+
python-version: "3.14"
|
|
180
|
+
|
|
181
|
+
- name: Build package
|
|
182
|
+
run: |
|
|
183
|
+
pip install build
|
|
184
|
+
python -m build
|
|
185
|
+
|
|
186
|
+
- name: Publish to PyPI
|
|
187
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
188
|
+
with:
|
|
189
|
+
skip-existing: true
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Version label check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, labeled, unlabeled]
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: version-label-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
source-branch:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Only development can target main
|
|
17
|
+
if: github.head_ref != 'development'
|
|
18
|
+
env:
|
|
19
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
20
|
+
run: |
|
|
21
|
+
echo "::error::Only the 'development' branch can target 'main'. Got '${HEAD_REF}'."
|
|
22
|
+
exit 1
|
|
23
|
+
|
|
24
|
+
version-label:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- name: Require exactly one release label
|
|
28
|
+
env:
|
|
29
|
+
LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }}
|
|
30
|
+
run: |
|
|
31
|
+
no_release=$(echo "$LABELS" | jq '[.[] | select(. == "no-release")] | length')
|
|
32
|
+
version=$(echo "$LABELS" | jq '[.[] | select(. == "major" or . == "minor" or . == "patch")] | length')
|
|
33
|
+
|
|
34
|
+
if [ "$no_release" -ge 1 ] && [ "$version" -ge 1 ]; then
|
|
35
|
+
echo "::error::Cannot combine 'no-release' with a version label."
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [ "$no_release" -eq 1 ]; then
|
|
40
|
+
echo "Label: no-release — skipping version bump and publish."
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [ "$version" -ne 1 ]; then
|
|
45
|
+
echo "::error::PR to main must have exactly one label: major, minor, patch, or no-release."
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
label=$(echo "$LABELS" | jq -r '[.[] | select(. == "major" or . == "minor" or . == "patch")][0]')
|
|
50
|
+
echo "Version label found: $label"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @antonio112009
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributing to pycrestron-cip
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! This document explains the workflow and guidelines.
|
|
4
|
+
|
|
5
|
+
## Branch Strategy
|
|
6
|
+
|
|
7
|
+
| Branch | Purpose |
|
|
8
|
+
|--------|---------|
|
|
9
|
+
| `main` | Stable release branch. Protected — no direct pushes. |
|
|
10
|
+
| `development` | Integration branch. All feature work merges here first. |
|
|
11
|
+
| `feature/*` | Short-lived branches for individual changes. |
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
1. **Create a feature branch** from `development`:
|
|
16
|
+
```bash
|
|
17
|
+
git checkout development && git pull
|
|
18
|
+
git checkout -b feature/my-change
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. **Make your changes**, commit, and push:
|
|
22
|
+
```bash
|
|
23
|
+
git push origin feature/my-change
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. **Open a Pull Request** targeting `development`. CI (lint + tests) must pass before merging.
|
|
27
|
+
|
|
28
|
+
4. **When ready to release**, open a Pull Request from `development` → `main`. CI runs again.
|
|
29
|
+
|
|
30
|
+
5. **On merge to `main`**, a release is created automatically:
|
|
31
|
+
- Version in `__version__.py` is bumped from the PR label
|
|
32
|
+
- A git tag and GitHub release are created
|
|
33
|
+
- Add exactly one of `patch`, `minor`, or `major` to the PR before merging
|
|
34
|
+
|
|
35
|
+
## Development Setup
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Clone the repo
|
|
39
|
+
git clone https://github.com/Antonio112009/pycrestron-cip.git
|
|
40
|
+
cd pycrestron-cip
|
|
41
|
+
|
|
42
|
+
# Install in editable mode with dev dependencies
|
|
43
|
+
pip install -e ".[dev]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Running Tests
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pytest
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Linting
|
|
53
|
+
|
|
54
|
+
This project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ruff check .
|
|
58
|
+
ruff format --check .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Code Style
|
|
62
|
+
|
|
63
|
+
- Python 3.13+, no runtime dependencies: the library must stay easy to install inside Home Assistant
|
|
64
|
+
- Line length: 120 characters
|
|
65
|
+
- Follow existing patterns in the codebase
|
|
66
|
+
- Add tests for new functionality. Byte vectors should come from real captures where possible;
|
|
67
|
+
say which processor they come from
|
|
68
|
+
- Never commit captures that contain addresses, passwords or names from a real installation
|
|
69
|
+
|
|
70
|
+
## Project Structure
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
src/pycrestron_cip/
|
|
74
|
+
├── client.py # CipClient: connection, sync, heartbeats, reconnect, join caches
|
|
75
|
+
├── protocol.py # Frame encoding/decoding (no I/O)
|
|
76
|
+
├── exceptions.py # Exception hierarchy
|
|
77
|
+
├── probe.py # pycrestron-cip-probe: read-only recording tool
|
|
78
|
+
└── testing.py # FakeProcessor: a CIP server for tests
|
|
79
|
+
docs/protocol.md # Wire format notes and hardware findings
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antonio112009
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|