pysnapdog 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.
- pysnapdog-0.1.0/.editorconfig +19 -0
- pysnapdog-0.1.0/.github/CODEOWNERS +1 -0
- pysnapdog-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- pysnapdog-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +16 -0
- pysnapdog-0.1.0/.github/dependabot.yml +48 -0
- pysnapdog-0.1.0/.github/pull_request_template.md +18 -0
- pysnapdog-0.1.0/.github/workflows/ci.yml +145 -0
- pysnapdog-0.1.0/.github/workflows/release-please.yml +47 -0
- pysnapdog-0.1.0/.github/workflows/release.yml +92 -0
- pysnapdog-0.1.0/.gitignore +56 -0
- pysnapdog-0.1.0/.python-version +1 -0
- pysnapdog-0.1.0/.release-please-manifest.json +3 -0
- pysnapdog-0.1.0/CHANGELOG.md +15 -0
- pysnapdog-0.1.0/CONTRIBUTING.md +59 -0
- pysnapdog-0.1.0/LICENSE +202 -0
- pysnapdog-0.1.0/PKG-INFO +218 -0
- pysnapdog-0.1.0/README.md +193 -0
- pysnapdog-0.1.0/SECURITY.md +26 -0
- pysnapdog-0.1.0/lefthook.yml +32 -0
- pysnapdog-0.1.0/pyproject.toml +91 -0
- pysnapdog-0.1.0/release-please-config.json +21 -0
- pysnapdog-0.1.0/scripts/check_coverage.py +37 -0
- pysnapdog-0.1.0/scripts/hooks/check-commit-message.sh +65 -0
- pysnapdog-0.1.0/scripts/hooks/check-push-secrets.sh +33 -0
- pysnapdog-0.1.0/scripts/hooks/check-staged.sh +48 -0
- pysnapdog-0.1.0/spec/asyncapi.json +657 -0
- pysnapdog-0.1.0/spec/openapi.json +6356 -0
- pysnapdog-0.1.0/src/pysnapdog/__init__.py +97 -0
- pysnapdog-0.1.0/src/pysnapdog/client.py +575 -0
- pysnapdog-0.1.0/src/pysnapdog/events.py +257 -0
- pysnapdog-0.1.0/src/pysnapdog/exceptions.py +44 -0
- pysnapdog-0.1.0/src/pysnapdog/listener.py +195 -0
- pysnapdog-0.1.0/src/pysnapdog/models.py +525 -0
- pysnapdog-0.1.0/src/pysnapdog/py.typed +0 -0
- pysnapdog-0.1.0/tests/__init__.py +0 -0
- pysnapdog-0.1.0/tests/conftest.py +147 -0
- pysnapdog-0.1.0/tests/coverage.py +97 -0
- pysnapdog-0.1.0/tests/spec.py +67 -0
- pysnapdog-0.1.0/tests/test_client.py +284 -0
- pysnapdog-0.1.0/tests/test_coverage.py +37 -0
- pysnapdog-0.1.0/tests/test_events.py +93 -0
- pysnapdog-0.1.0/tests/test_listener.py +419 -0
- pysnapdog-0.1.0/tests/test_models.py +158 -0
- pysnapdog-0.1.0/tests/test_operations.py +300 -0
- pysnapdog-0.1.0/uv.lock +999 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
indent_style = space
|
|
7
|
+
indent_size = 4
|
|
8
|
+
insert_final_newline = true
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
max_line_length = 100
|
|
11
|
+
|
|
12
|
+
[*.{toml,yml,yaml,json,jsonc}]
|
|
13
|
+
indent_size = 2
|
|
14
|
+
|
|
15
|
+
[*.{ts,tsx,js,mjs,cjs}]
|
|
16
|
+
indent_size = 2
|
|
17
|
+
|
|
18
|
+
[*.md]
|
|
19
|
+
trim_trailing_whitespace = false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @metaneutrons
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report something that is not working as expected
|
|
4
|
+
title: "[Bug] "
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Describe the bug**
|
|
9
|
+
A clear, short description of what is wrong.
|
|
10
|
+
|
|
11
|
+
**To reproduce**
|
|
12
|
+
Steps to reproduce the behavior:
|
|
13
|
+
1. ...
|
|
14
|
+
2. ...
|
|
15
|
+
|
|
16
|
+
**Expected behavior**
|
|
17
|
+
What you expected to happen instead.
|
|
18
|
+
|
|
19
|
+
**Actual behavior**
|
|
20
|
+
What actually happened (error messages, logs, screenshots).
|
|
21
|
+
|
|
22
|
+
**Environment**
|
|
23
|
+
- pysnapdog version:
|
|
24
|
+
- SnapDog server version:
|
|
25
|
+
- Python version:
|
|
26
|
+
- OS / platform:
|
|
27
|
+
- Minimal code that shows the problem (redact the API key):
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea or enhancement for this project
|
|
4
|
+
title: "[Feature] "
|
|
5
|
+
labels: enhancement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Problem**
|
|
9
|
+
What problem or limitation are you running into? Why does it matter?
|
|
10
|
+
|
|
11
|
+
**Proposed solution**
|
|
12
|
+
What you would like to happen. Describe the feature or change you have in
|
|
13
|
+
mind.
|
|
14
|
+
|
|
15
|
+
**Alternatives considered**
|
|
16
|
+
Any alternative solutions or workarounds you have considered.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Template. Goes to .github/dependabot.yml; delete the ecosystems the project
|
|
2
|
+
# does not have.
|
|
3
|
+
#
|
|
4
|
+
# The grouping is deliberately separated by update kind. One group with
|
|
5
|
+
# `patterns: ['*']` across all kinds bundles breaking changes with harmless
|
|
6
|
+
# bumps, and one broken dependency then blocks the whole stack. Minor and patch
|
|
7
|
+
# stay bundled, because checking them individually gains nothing there. Major
|
|
8
|
+
# stays ungrouped, so that each case arrives on its own.
|
|
9
|
+
#
|
|
10
|
+
# `cooldown` keeps a version that is withdrawn shortly after publication out of
|
|
11
|
+
# every pull request.
|
|
12
|
+
# Without a `timezone`, times are in UTC. Anyone needing local execution times
|
|
13
|
+
# enters a suitable IANA time zone per project.
|
|
14
|
+
|
|
15
|
+
version: 2
|
|
16
|
+
|
|
17
|
+
updates:
|
|
18
|
+
- package-ecosystem: uv
|
|
19
|
+
directory: /
|
|
20
|
+
schedule:
|
|
21
|
+
interval: weekly
|
|
22
|
+
day: monday
|
|
23
|
+
time: '05:00'
|
|
24
|
+
open-pull-requests-limit: 5
|
|
25
|
+
cooldown:
|
|
26
|
+
default-days: 3
|
|
27
|
+
semver-major-days: 7
|
|
28
|
+
groups:
|
|
29
|
+
python-minor-patch:
|
|
30
|
+
patterns: ['*']
|
|
31
|
+
update-types: [minor, patch]
|
|
32
|
+
|
|
33
|
+
- package-ecosystem: github-actions
|
|
34
|
+
directory: /
|
|
35
|
+
schedule:
|
|
36
|
+
interval: weekly
|
|
37
|
+
day: monday
|
|
38
|
+
time: '05:15'
|
|
39
|
+
open-pull-requests-limit: 5
|
|
40
|
+
cooldown:
|
|
41
|
+
default-days: 3
|
|
42
|
+
# semver-major-days is not permitted for github-actions. If it stands
|
|
43
|
+
# here, Dependabot rejects the ENTIRE file and not a single update runs
|
|
44
|
+
# any more, not even a security update.
|
|
45
|
+
groups:
|
|
46
|
+
github-actions-minor-patch:
|
|
47
|
+
patterns: ['*']
|
|
48
|
+
update-types: [minor, patch]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## What changed
|
|
2
|
+
|
|
3
|
+
<!-- Briefly describe the change. -->
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
<!-- What problem does this solve, or what does it improve? -->
|
|
8
|
+
|
|
9
|
+
## How it was tested
|
|
10
|
+
|
|
11
|
+
<!-- e.g. `uv run pytest`, `uv run ruff check .`, `uv run mypy`, manual verification, new/updated tests. -->
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
- [ ] The PR title follows [Conventional Commits](https://www.conventionalcommits.org/)
|
|
16
|
+
(`type(scope): description`, `!` or `BREAKING CHANGE:` for breaking
|
|
17
|
+
changes) — it becomes the commit message on `main` after squash merge
|
|
18
|
+
and drives the release version and changelog.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
quality:
|
|
18
|
+
name: Lint, types, tests (Python ${{ matrix.python }})
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
python: ['3.14']
|
|
23
|
+
runs-on: ubuntu-24.04
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
26
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python }}
|
|
29
|
+
enable-cache: true
|
|
30
|
+
# --locked fails when uv.lock does not match pyproject.toml. That is
|
|
31
|
+
# exactly what it is for.
|
|
32
|
+
- run: uv sync --locked
|
|
33
|
+
- run: uv run ruff check .
|
|
34
|
+
- run: uv run ruff format --check .
|
|
35
|
+
- name: Check the hook configuration
|
|
36
|
+
run: uv run lefthook validate
|
|
37
|
+
- run: uv run mypy
|
|
38
|
+
# The coverage floor (fail_under) lives in pyproject.toml.
|
|
39
|
+
- run: uv run pytest --cov
|
|
40
|
+
- name: Coverage of the server contracts
|
|
41
|
+
run: uv run python scripts/check_coverage.py
|
|
42
|
+
- name: Check the distribution content
|
|
43
|
+
run: |
|
|
44
|
+
set -euo pipefail
|
|
45
|
+
uv build --out-dir "$RUNNER_TEMP/dist"
|
|
46
|
+
unzip -l "$RUNNER_TEMP"/dist/*.whl | grep -E 'pysnapdog/(py.typed|client.py)'
|
|
47
|
+
tar -tzf "$RUNNER_TEMP"/dist/*.tar.gz | sort
|
|
48
|
+
|
|
49
|
+
security:
|
|
50
|
+
name: Secrets and advisories
|
|
51
|
+
runs-on: ubuntu-24.04
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
54
|
+
with:
|
|
55
|
+
fetch-depth: 0
|
|
56
|
+
# The gitleaks action wants a licence for organisation repositories. The CLI it wraps
|
|
57
|
+
# does not, so run the pinned release binary and verify its checksum.
|
|
58
|
+
- name: Scan the history for secrets
|
|
59
|
+
run: |
|
|
60
|
+
set -euo pipefail
|
|
61
|
+
version=8.30.1
|
|
62
|
+
archive="$RUNNER_TEMP/gitleaks.tar.gz"
|
|
63
|
+
curl -sSfL -o "$archive" \
|
|
64
|
+
"https://github.com/gitleaks/gitleaks/releases/download/v${version}/gitleaks_${version}_linux_x64.tar.gz"
|
|
65
|
+
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb $archive" | sha256sum -c -
|
|
66
|
+
tar -xzf "$archive" -C "$RUNNER_TEMP" gitleaks
|
|
67
|
+
"$RUNNER_TEMP/gitleaks" detect --source . --redact --no-banner --verbose
|
|
68
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
69
|
+
with:
|
|
70
|
+
python-version: '3.14'
|
|
71
|
+
- run: uv sync --locked
|
|
72
|
+
- run: uv audit --preview-features audit-command
|
|
73
|
+
|
|
74
|
+
commit-hygiene:
|
|
75
|
+
name: Commit hygiene
|
|
76
|
+
if: github.event_name == 'pull_request'
|
|
77
|
+
runs-on: ubuntu-24.04
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
80
|
+
with:
|
|
81
|
+
fetch-depth: 0
|
|
82
|
+
- name: Check title, body and every commit
|
|
83
|
+
# Title and body reach the script exclusively through env, never by
|
|
84
|
+
# interpolation into the script text: otherwise the pull request title
|
|
85
|
+
# would be a shell injection vector.
|
|
86
|
+
env:
|
|
87
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
88
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
89
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
90
|
+
PR_BODY: ${{ github.event.pull_request.body }}
|
|
91
|
+
run: |
|
|
92
|
+
set -euo pipefail
|
|
93
|
+
status=0
|
|
94
|
+
cc='^((feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._/-]+\))?!?: .+|Revert ".+")'
|
|
95
|
+
# grep works line by line, hence .* rather than [^\n]*: in ERE
|
|
96
|
+
# [^\n] would be the class "neither a backslash nor an n" and would
|
|
97
|
+
# exclude names containing an 'n'. In the RE2 rule of the ruleset
|
|
98
|
+
# [^\n] is the correct one. Anchored at the start of the line: by
|
|
99
|
+
# convention a trailer stands on a line of its own. Without the anchor
|
|
100
|
+
# the rule also fires when a pull request body merely mentions the
|
|
101
|
+
# forbidden trailer, for instance to describe a doctor finding, and it
|
|
102
|
+
# then rejects a valid input.
|
|
103
|
+
ban='^[[:space:]]*co-authored-by:.*(claude|anthropic)|^[[:space:]]*generated with.*claude code'
|
|
104
|
+
|
|
105
|
+
# On a squash merge the pull request title becomes the subject line
|
|
106
|
+
# on main and thereby determines the next version.
|
|
107
|
+
if ! printf '%s' "$PR_TITLE" | grep -Eq "$cc"; then
|
|
108
|
+
echo "::error::pull request title does not follow Conventional Commits: $PR_TITLE"
|
|
109
|
+
status=1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# With squash_merge_commit_message=PR_BODY the body lands in the commit.
|
|
113
|
+
if printf '%s' "$PR_BODY" | grep -Eiq "$ban"; then
|
|
114
|
+
echo "::error::the pull request body contains an AI attribution trailer."
|
|
115
|
+
status=1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
for sha in $(git rev-list "${BASE_SHA}..${HEAD_SHA}"); do
|
|
119
|
+
if git log -1 --format=%B "$sha" | grep -Eiq "$ban"; then
|
|
120
|
+
echo "::error::commit $sha contains an AI attribution trailer."
|
|
121
|
+
status=1
|
|
122
|
+
fi
|
|
123
|
+
email=$(git log -1 --format='%ae%n%ce' "$sha")
|
|
124
|
+
if printf '%s' "$email" | grep -Fq 'noreply@anthropic.com'; then
|
|
125
|
+
echo "::error::commit $sha has an Anthropic address as author or committer."
|
|
126
|
+
status=1
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
if [ "$status" -ne 0 ]; then
|
|
131
|
+
echo "::notice::switch this off for good in ~/.claude/settings.json: {\"attribution\":{\"commit\":\"\",\"pr\":\"\"}}"
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
ci-success:
|
|
136
|
+
name: CI Success
|
|
137
|
+
if: always()
|
|
138
|
+
needs: [quality, security, commit-hygiene]
|
|
139
|
+
runs-on: ubuntu-24.04
|
|
140
|
+
steps:
|
|
141
|
+
- name: Every preceding job must have succeeded or been skipped
|
|
142
|
+
run: |
|
|
143
|
+
set -euo pipefail
|
|
144
|
+
printf '%s\n' '${{ toJSON(needs) }}' |
|
|
145
|
+
jq -e 'all(.[]; .result == "success" or .result == "skipped")'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# The only automatic route by which a production tag comes about.
|
|
2
|
+
name: Release Please
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
pull-requests: write
|
|
15
|
+
actions: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
release-please:
|
|
19
|
+
runs-on: ubuntu-24.04
|
|
20
|
+
# Per the non-negotiable rule the app credentials live exclusively in this
|
|
21
|
+
# environment. Without the declaration they are invisible to the job.
|
|
22
|
+
environment: release
|
|
23
|
+
steps:
|
|
24
|
+
# The release pull request MUST be created with an app token. GitHub
|
|
25
|
+
# suppresses workflow triggers from GITHUB_TOKEN pushes as recursion
|
|
26
|
+
# protection; CI would then never run on the release pull request and the
|
|
27
|
+
# required checks would stay pending. An app token is a genuine actor.
|
|
28
|
+
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3
|
|
29
|
+
id: app-token
|
|
30
|
+
with:
|
|
31
|
+
client-id: ${{ vars.RELEASE_PLEASE_CLIENT_ID }}
|
|
32
|
+
private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }}
|
|
33
|
+
|
|
34
|
+
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
|
|
35
|
+
id: rp
|
|
36
|
+
with:
|
|
37
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
38
|
+
config-file: release-please-config.json
|
|
39
|
+
manifest-file: .release-please-manifest.json
|
|
40
|
+
|
|
41
|
+
# Hand the draft release to the publish workflow. The tag exists (force-tag-creation).
|
|
42
|
+
- name: Start the release workflow
|
|
43
|
+
if: steps.rp.outputs.release_created == 'true'
|
|
44
|
+
env:
|
|
45
|
+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
46
|
+
TAG: ${{ steps.rp.outputs.tag_name }}
|
|
47
|
+
run: gh workflow run release.yml --repo "$GITHUB_REPOSITORY" -f tag="$TAG"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Publishes a release to PyPI. Started by release-please.yml for the draft release it created,
|
|
2
|
+
# or by hand for a tag that exists. The first upload uses the PYPI_TOKEN of the release
|
|
3
|
+
# environment. Once the project exists on PyPI, a trusted publisher replaces the token
|
|
4
|
+
# (id-token: write, no password) and the secret is deleted.
|
|
5
|
+
name: Release
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
tag:
|
|
11
|
+
description: Release tag, for example v0.1.0
|
|
12
|
+
required: true
|
|
13
|
+
type: string
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: release-${{ inputs.tag }}
|
|
20
|
+
cancel-in-progress: false
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
publish:
|
|
24
|
+
runs-on: ubuntu-24.04
|
|
25
|
+
environment: release
|
|
26
|
+
steps:
|
|
27
|
+
- name: Preflight
|
|
28
|
+
env:
|
|
29
|
+
TAG: ${{ inputs.tag }}
|
|
30
|
+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
31
|
+
GH_TOKEN: ${{ github.token }}
|
|
32
|
+
run: |
|
|
33
|
+
set -euo pipefail
|
|
34
|
+
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "bad tag: $TAG"; exit 1; }
|
|
35
|
+
[ -n "$PYPI_TOKEN" ] || { echo "PYPI_TOKEN is empty"; exit 1; }
|
|
36
|
+
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
|
|
37
|
+
|
|
38
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
39
|
+
with:
|
|
40
|
+
ref: refs/tags/${{ inputs.tag }}
|
|
41
|
+
|
|
42
|
+
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
|
|
43
|
+
with:
|
|
44
|
+
python-version: '3.14'
|
|
45
|
+
|
|
46
|
+
- name: Check that the tag and the package agree
|
|
47
|
+
env:
|
|
48
|
+
TAG: ${{ inputs.tag }}
|
|
49
|
+
run: |
|
|
50
|
+
set -euo pipefail
|
|
51
|
+
version=$(uv run --no-project python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
52
|
+
[ "v$version" = "$TAG" ] || { echo "pyproject says $version, the tag is $TAG"; exit 1; }
|
|
53
|
+
|
|
54
|
+
- name: Test and build
|
|
55
|
+
run: |
|
|
56
|
+
set -euo pipefail
|
|
57
|
+
uv sync --locked
|
|
58
|
+
uv run pytest --cov
|
|
59
|
+
uv build --out-dir dist
|
|
60
|
+
|
|
61
|
+
- name: Upload to PyPI
|
|
62
|
+
env:
|
|
63
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
64
|
+
run: uv publish dist/*
|
|
65
|
+
|
|
66
|
+
- name: Verify that PyPI serves exactly what was built
|
|
67
|
+
env:
|
|
68
|
+
TAG: ${{ inputs.tag }}
|
|
69
|
+
run: |
|
|
70
|
+
set -euo pipefail
|
|
71
|
+
version="${TAG#v}"
|
|
72
|
+
for file in dist/*; do
|
|
73
|
+
name=$(basename "$file")
|
|
74
|
+
want=$(sha256sum "$file" | cut -d' ' -f1)
|
|
75
|
+
for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
|
76
|
+
got=$(curl -sSf "https://pypi.org/pypi/pysnapdog/${version}/json" \
|
|
77
|
+
| jq -r --arg n "$name" '.urls[] | select(.filename==$n) | .digests.sha256' || true)
|
|
78
|
+
[ "$got" = "$want" ] && break
|
|
79
|
+
sleep 10
|
|
80
|
+
done
|
|
81
|
+
[ "$got" = "$want" ] || { echo "$name: PyPI has '$got', built $want"; exit 1; }
|
|
82
|
+
echo "$name verified"
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
- name: Publish the GitHub release
|
|
86
|
+
env:
|
|
87
|
+
TAG: ${{ inputs.tag }}
|
|
88
|
+
GH_TOKEN: ${{ github.token }}
|
|
89
|
+
run: |
|
|
90
|
+
set -euo pipefail
|
|
91
|
+
gh release upload "$TAG" dist/* --clobber --repo "$GITHUB_REPOSITORY"
|
|
92
|
+
gh release edit "$TAG" --draft=false --repo "$GITHUB_REPOSITORY"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Patterns deliberately UNANCHORED, so that they bite at every level.
|
|
2
|
+
# Canonical inputs and generators stay tracked; reproducible outputs do not.
|
|
3
|
+
# uv.lock and .python-version are tracked on purpose.
|
|
4
|
+
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.eggs/
|
|
13
|
+
|
|
14
|
+
# Tool caches and coverage
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
.coverage.*
|
|
20
|
+
coverage.xml
|
|
21
|
+
htmlcov/
|
|
22
|
+
|
|
23
|
+
# Environment and state
|
|
24
|
+
.env
|
|
25
|
+
.env.*
|
|
26
|
+
!.env.example
|
|
27
|
+
*.log
|
|
28
|
+
|
|
29
|
+
# Editor and operating system
|
|
30
|
+
.DS_Store
|
|
31
|
+
.AppleDouble
|
|
32
|
+
.LSOverride
|
|
33
|
+
._*
|
|
34
|
+
.DocumentRevisions-V100/
|
|
35
|
+
.fseventsd/
|
|
36
|
+
.Spotlight-V100/
|
|
37
|
+
.TemporaryItems/
|
|
38
|
+
.Trashes/
|
|
39
|
+
.VolumeIcon.icns
|
|
40
|
+
.com.apple.timemachine.donotpresent
|
|
41
|
+
Thumbs.db
|
|
42
|
+
Thumbs.db:encryptable
|
|
43
|
+
ehthumbs.db
|
|
44
|
+
ehthumbs_vista.db
|
|
45
|
+
[Dd]esktop.ini
|
|
46
|
+
$RECYCLE.BIN/
|
|
47
|
+
*.stackdump
|
|
48
|
+
.fuse_hidden*
|
|
49
|
+
.directory
|
|
50
|
+
.Trash-*
|
|
51
|
+
.nfs*
|
|
52
|
+
.idea/
|
|
53
|
+
.vscode/
|
|
54
|
+
*.swp
|
|
55
|
+
*.swo
|
|
56
|
+
*~
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-11-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* bootstrap the repository ([4105e40](https://github.com/SnapDogRocks/pysnapdog/commit/4105e4044bdc9d445b11a2de33d6f3b077e182d0))
|
|
9
|
+
* cover the REST API and the WebSocket events of the server ([72cf902](https://github.com/SnapDogRocks/pysnapdog/commit/72cf90263d862c2d5e58a8e11137cbd56d5bd492))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Documentation
|
|
13
|
+
|
|
14
|
+
* align the README header with the server repository ([c0c5385](https://github.com/SnapDogRocks/pysnapdog/commit/c0c538564124faf9e54837a65fbae0d968998f12))
|
|
15
|
+
* drop the not-yet-released notes from the README ([#4](https://github.com/SnapDogRocks/pysnapdog/issues/4)) ([2a045cf](https://github.com/SnapDogRocks/pysnapdog/commit/2a045cf860fc0beba9e58d597c98462d5e677db3))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. The library is small on purpose, so most changes are quick to review.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
The project uses [uv](https://docs.astral.sh/uv/) and Python 3.14.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync
|
|
11
|
+
uv run lefthook install # local hooks; needs gitleaks (brew install gitleaks)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Checks
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run ruff check .
|
|
18
|
+
uv run ruff format --check .
|
|
19
|
+
uv run mypy
|
|
20
|
+
uv run pytest --cov # coverage floor 95 %
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
CI runs the same and `uv audit`. The hooks run a subset: `pre-commit` stays under three seconds,
|
|
24
|
+
`pre-push` runs mypy and the tests. If a hook is in your way, tell us in the pull request instead
|
|
25
|
+
of reaching for `--no-verify`.
|
|
26
|
+
|
|
27
|
+
## Commit messages: Conventional Commits
|
|
28
|
+
|
|
29
|
+
Every pull request title follows [Conventional Commits](https://www.conventionalcommits.org/):
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
<type>(<scope>): <description>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `scope` is optional, lower case.
|
|
36
|
+
- Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`,
|
|
37
|
+
`revert`.
|
|
38
|
+
- A breaking change is marked with `!` after the type or a `BREAKING CHANGE:` footer.
|
|
39
|
+
|
|
40
|
+
Pull requests are squash-merged, so the **title** becomes the commit on `main`, and
|
|
41
|
+
release-please reads it to pick the next version and the changelog entry. Commit messages,
|
|
42
|
+
pull request texts and branch names are written in English.
|
|
43
|
+
|
|
44
|
+
One pull request carries one kind of change. A fix that travels with a `feat` is filed under
|
|
45
|
+
Features in the changelog, where nobody looks for it.
|
|
46
|
+
|
|
47
|
+
## Branches
|
|
48
|
+
|
|
49
|
+
`type/short-description`, with the same types as above, for example `feat/zone-control`.
|
|
50
|
+
|
|
51
|
+
## Tests
|
|
52
|
+
|
|
53
|
+
The tests run against a fake server (`tests/conftest.py`) whose answers follow the server's
|
|
54
|
+
`openapi.json`. When you add an endpoint, add the fake route, a model if the response needs one,
|
|
55
|
+
and a test for the success path and for each way it can fail.
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
By contributing you agree that your work is licensed under the [Apache License 2.0](LICENSE).
|