PyAgoraRTC 0.2.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.
- pyagorartc-0.2.0/.claude/agents/code-reviewer.md +43 -0
- pyagorartc-0.2.0/.claude/agents/test-reviewer.md +48 -0
- pyagorartc-0.2.0/.github/workflows/ci.yml +20 -0
- pyagorartc-0.2.0/.github/workflows/release.yml +73 -0
- pyagorartc-0.2.0/.gitignore +207 -0
- pyagorartc-0.2.0/.pre-commit-config.yaml +21 -0
- pyagorartc-0.2.0/.python-version +1 -0
- pyagorartc-0.2.0/CLAUDE.md +56 -0
- pyagorartc-0.2.0/CONSTITUTION.md +89 -0
- pyagorartc-0.2.0/CONTRIBUTING.md +23 -0
- pyagorartc-0.2.0/LICENSE +674 -0
- pyagorartc-0.2.0/PKG-INFO +151 -0
- pyagorartc-0.2.0/README.md +128 -0
- pyagorartc-0.2.0/docs/analysis/divergence.md +416 -0
- pyagorartc-0.2.0/docs/analysis/legacy/README.md +6 -0
- pyagorartc-0.2.0/docs/analysis/legacy/agora_api.py +852 -0
- pyagorartc-0.2.0/docs/analysis/legacy/agora_sdp.py +527 -0
- pyagorartc-0.2.0/docs/analysis/legacy/agora_websockets.py +1775 -0
- pyagorartc-0.2.0/docs/architecture.md +181 -0
- pyagorartc-0.2.0/docs/backlog.md +45 -0
- pyagorartc-0.2.0/docs/code_style.md +119 -0
- pyagorartc-0.2.0/docs/decisions.md +241 -0
- pyagorartc-0.2.0/docs/migration.md +683 -0
- pyagorartc-0.2.0/docs/open_questions.md +138 -0
- pyagorartc-0.2.0/docs/protocol.md +1166 -0
- pyagorartc-0.2.0/docs/testing.md +167 -0
- pyagorartc-0.2.0/pyagorartc/__init__.py +67 -0
- pyagorartc-0.2.0/pyagorartc/ap/__init__.py +17 -0
- pyagorartc-0.2.0/pyagorartc/ap/client.py +244 -0
- pyagorartc-0.2.0/pyagorartc/ap/password.py +14 -0
- pyagorartc-0.2.0/pyagorartc/ap/response.py +329 -0
- pyagorartc-0.2.0/pyagorartc/const.py +55 -0
- pyagorartc-0.2.0/pyagorartc/exceptions.py +61 -0
- pyagorartc-0.2.0/pyagorartc/models.py +223 -0
- pyagorartc-0.2.0/pyagorartc/py.typed +0 -0
- pyagorartc-0.2.0/pyagorartc/rtm/__init__.py +5 -0
- pyagorartc-0.2.0/pyagorartc/rtm/client.py +170 -0
- pyagorartc-0.2.0/pyagorartc/sdp/__init__.py +26 -0
- pyagorartc-0.2.0/pyagorartc/sdp/answer.py +320 -0
- pyagorartc-0.2.0/pyagorartc/sdp/candidates.py +103 -0
- pyagorartc-0.2.0/pyagorartc/sdp/offer.py +260 -0
- pyagorartc-0.2.0/pyagorartc/session/__init__.py +87 -0
- pyagorartc-0.2.0/pyagorartc/session/messages.py +570 -0
- pyagorartc-0.2.0/pyagorartc/session/recovery.py +166 -0
- pyagorartc-0.2.0/pyagorartc/session/session.py +696 -0
- pyagorartc-0.2.0/pyagorartc/session/transport.py +130 -0
- pyagorartc-0.2.0/pyproject.toml +140 -0
- pyagorartc-0.2.0/tests/__init__.py +1 -0
- pyagorartc-0.2.0/tests/_helpers.py +63 -0
- pyagorartc-0.2.0/tests/conftest.py +31 -0
- pyagorartc-0.2.0/tests/fakegateway/__init__.py +6 -0
- pyagorartc-0.2.0/tests/fakegateway/__main__.py +20 -0
- pyagorartc-0.2.0/tests/fakegateway/_common.py +93 -0
- pyagorartc-0.2.0/tests/fakegateway/ap.py +155 -0
- pyagorartc-0.2.0/tests/fakegateway/gateway.py +360 -0
- pyagorartc-0.2.0/tests/fakegateway/rtm.py +81 -0
- pyagorartc-0.2.0/tests/fakegateway/scheduler.py +102 -0
- pyagorartc-0.2.0/tests/fakegateway/server.py +165 -0
- pyagorartc-0.2.0/tests/fakegateway/state.py +222 -0
- pyagorartc-0.2.0/tests/fixtures/README.md +61 -0
- pyagorartc-0.2.0/tests/fixtures/ap/choose_server_all_failed.json +43 -0
- pyagorartc-0.2.0/tests/fixtures/ap/choose_server_gateway_failed.json +56 -0
- pyagorartc-0.2.0/tests/fixtures/ap/choose_server_irregular.json +44 -0
- pyagorartc-0.2.0/tests/fixtures/ap/choose_server_one_failed.json +56 -0
- pyagorartc-0.2.0/tests/fixtures/ap/choose_server_request_expected.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/ap/choose_server_response.json +51 -0
- pyagorartc-0.2.0/tests/fixtures/ap/update_ticket_response.json +31 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/error.json +6 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_failed.json +8 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_failed_code_only.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_ok.json +116 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_ok_no_ortc.json +10 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_ok_rtx.json +109 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_v3_expected.json +124 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/join_v3_inputs.json +79 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/leave_expected.json +4 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/non_object_message.json +6 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_add_video_stream.json +13 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_add_video_stream_h265_pt0.json +13 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_add_video_stream_minimal.json +9 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_add_video_stream_no_ssrc.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_notification_quit.json +8 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_notification_warn.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_p2p_lost.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_p2p_lost_top_level.json +9 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_p2p_lost_top_level_only.json +5 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_p2p_ok.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_rtp_capability_change.json +11 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_rtp_capability_change_irregular.json +12 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_token_privilege_did_expire.json +4 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_token_privilege_will_expire.json +4 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_user_offline.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_user_offline_no_uid.json +6 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/on_user_online.json +6 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/ping_back.json +5 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/ping_expected.json +4 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/renew_token_expected.json +7 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/set_client_role_expected.json +9 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/subscribe_expected.json +15 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/unknown_event.json +11 -0
- pyagorartc-0.2.0/tests/fixtures/gateway/unsubscribe_expected.json +9 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/ack_delivered.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/ack_failed.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/ack_failed_sent.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/ack_mixed_case.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/ack_offline.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/ack_sent.json +1 -0
- pyagorartc-0.2.0/tests/fixtures/rtm/peer_message_request_expected.json +17 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/chrome_answer_shipped.sdp +46 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/chrome_offer_ortc_shipped.json +308 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/chrome_recvonly_offer.sdp +70 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/gateway_join_ortc.json +22 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/gateway_ortc_vp8.json +38 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/go2rtc_offer.sdp +45 -0
- pyagorartc-0.2.0/tests/fixtures/sdp/whep_trickle_fragment.sdpfrag +13 -0
- pyagorartc-0.2.0/tests/integration/__init__.py +1 -0
- pyagorartc-0.2.0/tests/integration/_helpers.py +285 -0
- pyagorartc-0.2.0/tests/integration/conftest.py +86 -0
- pyagorartc-0.2.0/tests/integration/test_ap_client.py +266 -0
- pyagorartc-0.2.0/tests/integration/test_fakegateway_contract_ap.py +188 -0
- pyagorartc-0.2.0/tests/integration/test_fakegateway_contract_control.py +314 -0
- pyagorartc-0.2.0/tests/integration/test_fakegateway_contract_gateway_events.py +218 -0
- pyagorartc-0.2.0/tests/integration/test_fakegateway_contract_gateway_join.py +220 -0
- pyagorartc-0.2.0/tests/integration/test_fakegateway_contract_rtm.py +122 -0
- pyagorartc-0.2.0/tests/integration/test_rtm_client.py +195 -0
- pyagorartc-0.2.0/tests/integration/test_session_endings.py +120 -0
- pyagorartc-0.2.0/tests/integration/test_session_join.py +167 -0
- pyagorartc-0.2.0/tests/integration/test_session_secrets.py +69 -0
- pyagorartc-0.2.0/tests/integration/test_session_streams.py +130 -0
- pyagorartc-0.2.0/tests/integration/test_session_timers.py +124 -0
- pyagorartc-0.2.0/tests/integration/test_transport.py +44 -0
- pyagorartc-0.2.0/tests/meta/__init__.py +1 -0
- pyagorartc-0.2.0/tests/meta/_helpers.py +232 -0
- pyagorartc-0.2.0/tests/meta/test_conventions.py +844 -0
- pyagorartc-0.2.0/tests/meta/test_guards.py +23 -0
- pyagorartc-0.2.0/tests/regression/__init__.py +1 -0
- pyagorartc-0.2.0/tests/unit/__init__.py +1 -0
- pyagorartc-0.2.0/tests/unit/_fakes.py +246 -0
- pyagorartc-0.2.0/tests/unit/ap/__init__.py +1 -0
- pyagorartc-0.2.0/tests/unit/ap/test_client.py +369 -0
- pyagorartc-0.2.0/tests/unit/ap/test_password.py +22 -0
- pyagorartc-0.2.0/tests/unit/ap/test_response.py +427 -0
- pyagorartc-0.2.0/tests/unit/conftest.py +28 -0
- pyagorartc-0.2.0/tests/unit/rtm/__init__.py +1 -0
- pyagorartc-0.2.0/tests/unit/rtm/test_client.py +361 -0
- pyagorartc-0.2.0/tests/unit/sdp/__init__.py +1 -0
- pyagorartc-0.2.0/tests/unit/sdp/_helpers.py +58 -0
- pyagorartc-0.2.0/tests/unit/sdp/test_answer.py +594 -0
- pyagorartc-0.2.0/tests/unit/sdp/test_candidates.py +172 -0
- pyagorartc-0.2.0/tests/unit/sdp/test_offer.py +364 -0
- pyagorartc-0.2.0/tests/unit/session/__init__.py +1 -0
- pyagorartc-0.2.0/tests/unit/session/_helpers.py +182 -0
- pyagorartc-0.2.0/tests/unit/session/test_messages.py +514 -0
- pyagorartc-0.2.0/tests/unit/session/test_recovery.py +249 -0
- pyagorartc-0.2.0/tests/unit/session/test_session.py +491 -0
- pyagorartc-0.2.0/tests/unit/session/test_session_recovery.py +176 -0
- pyagorartc-0.2.0/tests/unit/session/test_session_streams.py +325 -0
- pyagorartc-0.2.0/tests/unit/session/test_session_teardown.py +76 -0
- pyagorartc-0.2.0/tests/unit/session/test_session_timers.py +242 -0
- pyagorartc-0.2.0/tests/unit/session/test_transport.py +30 -0
- pyagorartc-0.2.0/tests/unit/test_models.py +197 -0
- pyagorartc-0.2.0/uv.lock +908 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-reviewer
|
|
3
|
+
description: Reviews a change in pyagorartc against CONSTITUTION.md, docs/architecture.md and docs/code_style.md. Reports findings by severity with file:line; does not rewrite. Launch over any non-trivial change before reporting it complete.
|
|
4
|
+
model: opus
|
|
5
|
+
tools: Read, Grep, Glob, Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You review code in the `pyagorartc` repository. Read `CONSTITUTION.md`,
|
|
9
|
+
`docs/architecture.md` §1–§3 and `docs/code_style.md` before looking at the
|
|
10
|
+
change. The change is the working-tree diff unless the prompt names files.
|
|
11
|
+
|
|
12
|
+
Check, in this order:
|
|
13
|
+
|
|
14
|
+
1. **Constitution violations** (blocking): layer direction, secrets in
|
|
15
|
+
logs/repr/exceptions, a non-success response returned instead of raised,
|
|
16
|
+
a retry timer or cooldown in auth, a second home for a concern listed in
|
|
17
|
+
architecture §3, host (Home Assistant) knowledge in the package.
|
|
18
|
+
2. **Contract with the spec** (blocking): field names and types against
|
|
19
|
+
`docs/api/<group>.md` and `docs/openapi/mower.json`; required vs optional;
|
|
20
|
+
enum values verbatim; timestamps as `*_ms: int`.
|
|
21
|
+
3. **Error mapping** (blocking): every status/envelope path maps to exactly
|
|
22
|
+
the exception in architecture §2.3; `TransportError` never mutates auth
|
|
23
|
+
state; `CredentialsRejectedError` is terminal and fails fast.
|
|
24
|
+
4. **Style** (major/minor): comments that restate code, docstrings missing
|
|
25
|
+
on public names, `Any` in public signatures, magic numbers, imports inside
|
|
26
|
+
functions, dead code, naming.
|
|
27
|
+
5. **Docs drift** (major): a change to behaviour without the matching edit in
|
|
28
|
+
`docs/`; a new choice without a `Dn`; a new unknown without a `Qn`.
|
|
29
|
+
|
|
30
|
+
Report as:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
BLOCKING
|
|
34
|
+
- path:line — finding. Why it matters. What the fix is (one line).
|
|
35
|
+
MAJOR
|
|
36
|
+
- ...
|
|
37
|
+
MINOR
|
|
38
|
+
- ...
|
|
39
|
+
OK — what is good and should stay.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Do not edit files. Do not pad with praise. If there is nothing blocking, say so
|
|
43
|
+
in the first line.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-reviewer
|
|
3
|
+
description: Reviews tests in pyagorartc against docs/testing.md. Reports findings by severity with file:line; does not rewrite. Launch over every test file written or modified before reporting the work complete.
|
|
4
|
+
model: opus
|
|
5
|
+
tools: Read, Grep, Glob, Bash
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You review tests in the `pyagorartc` repository against `docs/testing.md`,
|
|
9
|
+
which is the testing constitution. Read it first. The files under review are
|
|
10
|
+
named in the prompt; otherwise review every test file in the working-tree
|
|
11
|
+
diff.
|
|
12
|
+
|
|
13
|
+
Check:
|
|
14
|
+
|
|
15
|
+
1. **Tier and placement** (blocking): a unit test importing
|
|
16
|
+
`tests.fakeserver` or opening a socket; a test module that does not mirror
|
|
17
|
+
its source module (`tests/unit/<pkg>/test_<module>.py`); a regression test
|
|
18
|
+
without the marker, without a docstring saying what the code did wrong, or
|
|
19
|
+
that could not have been red before the fix.
|
|
20
|
+
2. **Doubles** (blocking): any `MagicMock`/`Mock`/`patch` on a collaborator
|
|
21
|
+
that `tests/unit/_fakes.py` covers; mocking the unit under test; asserting
|
|
22
|
+
on call plumbing where an outcome was available.
|
|
23
|
+
3. **Time and concurrency** (blocking): `asyncio.sleep(x)` with `x > 0`;
|
|
24
|
+
unbounded waits; reading the real clock; non-deterministic interleaving.
|
|
25
|
+
4. **Secrets** (blocking): a real-looking credential or hostname; a test that
|
|
26
|
+
does not assert redaction where the code redacts.
|
|
27
|
+
5. **Quality** (major/minor): a test that asserts nothing; several behaviours
|
|
28
|
+
in one test; names that do not read as a sentence; builders duplicated
|
|
29
|
+
instead of taken from `_helpers.py` / `_fakes.py`; fixtures with side
|
|
30
|
+
effects; missing negative cases for every documented exception.
|
|
31
|
+
6. **Coverage of the contract** (major): for each public method in the
|
|
32
|
+
source module, is there a test for the success path, each documented
|
|
33
|
+
exception, and the tolerant-decoding rule (unknown field, unknown enum)?
|
|
34
|
+
|
|
35
|
+
Run `uv run pytest <files> -q` and report the result. Report as:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
RESULT: <pytest summary line>
|
|
39
|
+
BLOCKING
|
|
40
|
+
- path:line — finding, why, fix.
|
|
41
|
+
MAJOR
|
|
42
|
+
- ...
|
|
43
|
+
MINOR
|
|
44
|
+
- ...
|
|
45
|
+
OK — what is good.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Do not edit files. The author fixes.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
checks:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: astral-sh/setup-uv@v5
|
|
14
|
+
with:
|
|
15
|
+
enable-cache: true
|
|
16
|
+
- run: uv sync --frozen
|
|
17
|
+
- run: uv run ruff check .
|
|
18
|
+
- run: uv run ruff format --check .
|
|
19
|
+
- run: uv run ty check pyagorartc/
|
|
20
|
+
- run: uv run pytest --cov --cov-report=term-missing
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
checks:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: astral-sh/setup-uv@v5
|
|
14
|
+
with:
|
|
15
|
+
enable-cache: true
|
|
16
|
+
- run: uv sync --frozen
|
|
17
|
+
- run: uv run ruff check .
|
|
18
|
+
- run: uv run ruff format --check .
|
|
19
|
+
- run: uv run ty check pyagorartc/
|
|
20
|
+
- run: uv run pytest
|
|
21
|
+
|
|
22
|
+
publish:
|
|
23
|
+
needs: checks
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
permissions:
|
|
26
|
+
id-token: write # trusted publishing to PyPI
|
|
27
|
+
contents: write # GitHub release
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: astral-sh/setup-uv@v5
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
- run: uv sync --frozen
|
|
34
|
+
|
|
35
|
+
# The tag and the package version are compared after PEP 440 normalisation,
|
|
36
|
+
# so v0.2.0-beta1, v0.2.0.b1 and v0.2.0b1 all match a 0.2.0b1 package.
|
|
37
|
+
- name: Check package version matches tag
|
|
38
|
+
id: version
|
|
39
|
+
run: |
|
|
40
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
41
|
+
uv run python - "$TAG_VERSION" <<'PY'
|
|
42
|
+
import importlib.metadata, os, sys
|
|
43
|
+
from packaging.version import InvalidVersion, Version
|
|
44
|
+
|
|
45
|
+
package_version = Version(importlib.metadata.version("pyagorartc"))
|
|
46
|
+
try:
|
|
47
|
+
tag_version = Version(sys.argv[1])
|
|
48
|
+
except InvalidVersion:
|
|
49
|
+
sys.exit(f"Tag version ({sys.argv[1]}) is not a valid PEP 440 version")
|
|
50
|
+
if package_version != tag_version:
|
|
51
|
+
sys.exit(f"Package version ({package_version}) does not match tag version ({tag_version})")
|
|
52
|
+
|
|
53
|
+
with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
|
|
54
|
+
fh.write(f"prerelease={str(package_version.is_prerelease).lower()}\n")
|
|
55
|
+
fh.write(f"version={package_version}\n")
|
|
56
|
+
PY
|
|
57
|
+
|
|
58
|
+
- run: uv build
|
|
59
|
+
|
|
60
|
+
- name: Publish package distributions to PyPI
|
|
61
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
62
|
+
with:
|
|
63
|
+
verbose: true
|
|
64
|
+
print-hash: true
|
|
65
|
+
|
|
66
|
+
- name: Create GitHub Release
|
|
67
|
+
uses: softprops/action-gh-release@v2
|
|
68
|
+
with:
|
|
69
|
+
files: dist/*
|
|
70
|
+
generate_release_notes: true
|
|
71
|
+
prerelease: ${{ steps.version.outputs.prerelease }}
|
|
72
|
+
env:
|
|
73
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.15.7
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: local
|
|
9
|
+
hooks:
|
|
10
|
+
- id: ty
|
|
11
|
+
name: ty
|
|
12
|
+
entry: uv run ty check pyagorartc/
|
|
13
|
+
language: system
|
|
14
|
+
pass_filenames: false
|
|
15
|
+
types: [python]
|
|
16
|
+
- id: unit-tests
|
|
17
|
+
name: unit tests
|
|
18
|
+
entry: uv run pytest tests/unit tests/meta -q
|
|
19
|
+
language: system
|
|
20
|
+
pass_filenames: false
|
|
21
|
+
types: [python]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for agents working in this repository. Read `CONSTITUTION.md` first;
|
|
4
|
+
it is short and it is binding.
|
|
5
|
+
|
|
6
|
+
## What this is
|
|
7
|
+
|
|
8
|
+
`pyagorartc`: an async Python client for Agora RTC signalling, reverse-engineered
|
|
9
|
+
from Agora's Web SDK and hardened by two Home Assistant integrations
|
|
10
|
+
(Mammotion mowers, PetKit cameras). It is host-agnostic: no Home Assistant, no
|
|
11
|
+
vendor library, no vendor behaviour except through documented extension
|
|
12
|
+
points.
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv sync
|
|
18
|
+
uv run ruff check --fix . && uv run ruff format .
|
|
19
|
+
uv run ty check pyagorartc/
|
|
20
|
+
uv run pytest # all tiers except live
|
|
21
|
+
uv run pytest tests/unit # fast tier
|
|
22
|
+
uv run pre-commit run --all-files
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Where things are
|
|
26
|
+
|
|
27
|
+
| Need | Read |
|
|
28
|
+
|---|---|
|
|
29
|
+
| the rules | `CONSTITUTION.md` |
|
|
30
|
+
| the map (layers, session lifecycle, single homes, recipes) | `docs/architecture.md` |
|
|
31
|
+
| the protocol as we know it | `docs/protocol.md`, `tests/fixtures/` |
|
|
32
|
+
| how to write code / tests | `docs/code_style.md`, `docs/testing.md` |
|
|
33
|
+
| why something is the way it is | `docs/decisions.md` (cite as D7) |
|
|
34
|
+
| what we do not know | `docs/open_questions.md` (cite as Q2) |
|
|
35
|
+
| how a host adopts the library | `docs/migration.md` |
|
|
36
|
+
| what is left to do | `docs/backlog.md` |
|
|
37
|
+
|
|
38
|
+
## Rules of work
|
|
39
|
+
|
|
40
|
+
- **Audit before adding.** Every concern has a single home
|
|
41
|
+
(`architecture.md` §3). Extend the existing site.
|
|
42
|
+
- **Traceability.** A change to what goes on the wire cites its source
|
|
43
|
+
(fixture, SDK behaviour, or a regression test that was red).
|
|
44
|
+
- **No host or vendor imports** anywhere under `pyagorartc/`. The meta tests
|
|
45
|
+
enforce it.
|
|
46
|
+
- **Tests before merge, in the right tier.** Regression tests are seen red
|
|
47
|
+
first and marked `regression`. Launch `test-reviewer` over any test file you
|
|
48
|
+
touched and fix its blocking findings before reporting done.
|
|
49
|
+
- **Reviews.** Non-trivial changes get `code-reviewer` before they are
|
|
50
|
+
reported complete. The author fixes; the reviewer does not rewrite.
|
|
51
|
+
- **Decisions are written down.** New choice → `Dn`; new unknown → `Qn`.
|
|
52
|
+
- **No secrets anywhere** in logs, reprs, exceptions, fixtures (redact), tests
|
|
53
|
+
or commit messages.
|
|
54
|
+
- **Comments say why, in one or two lines, or do not exist.**
|
|
55
|
+
- **Commits**: imperative subject, one change per commit, no attribution
|
|
56
|
+
trailers.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Constitution
|
|
2
|
+
|
|
3
|
+
The rules that do not bend. `docs/` explains how to apply them; this file says
|
|
4
|
+
what they are. Breaking one needs a new numbered entry in `docs/decisions.md`
|
|
5
|
+
that supersedes the rule, not a quiet exception.
|
|
6
|
+
|
|
7
|
+
## 1. Protocol fidelity is traceable
|
|
8
|
+
|
|
9
|
+
`pyagorartc` speaks a signalling protocol that Agora has not published for
|
|
10
|
+
Python. Every behaviour on the wire traces to one of: a recorded exchange
|
|
11
|
+
(`tests/fixtures/`), the documented behaviour of Agora's Web SDK, or a fix
|
|
12
|
+
that carries a regression test. A behaviour that traces to nothing is a guess,
|
|
13
|
+
and a guess is written down in `docs/open_questions.md` before it ships. The
|
|
14
|
+
hard-won fixes (MID extension stripping, DTLS role, no `set_client_role` after
|
|
15
|
+
join, msid stream id, peer recovery) are pinned by tests, not by comments.
|
|
16
|
+
|
|
17
|
+
## 2. One library, many vendors, no host
|
|
18
|
+
|
|
19
|
+
Mammotion mowers and PetKit cameras both publish through Agora; other vendors
|
|
20
|
+
will. The library knows channels, tokens, edges, SDP and gateway messages. It
|
|
21
|
+
does not know mowers, feeders, `pymammotion`, `pypetkitapi`, or Home
|
|
22
|
+
Assistant. Vendor-specific behaviour (a keep-alive the device needs, a way to
|
|
23
|
+
re-request the stream) enters through callbacks and options on the session,
|
|
24
|
+
never through an import. If a vendor needs something the session cannot
|
|
25
|
+
express, the session grows a documented extension point.
|
|
26
|
+
|
|
27
|
+
## 3. Layers point one way
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
sdp ← ap ← session ← (host) rtm ← (host)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`sdp/` is pure: text and dicts in, text and dicts out, no I/O (it may read
|
|
34
|
+
`const` values). `ap/` talks
|
|
35
|
+
HTTP to Agora's access points. `session/` drives one WebSocket gateway
|
|
36
|
+
session and composes the two. `rtm/` is independent of all three. Nothing
|
|
37
|
+
imports upward. The public surface is
|
|
38
|
+
`pyagorartc.__all__`.
|
|
39
|
+
|
|
40
|
+
## 4. All I/O is asynchronous and owned
|
|
41
|
+
|
|
42
|
+
Every network call is `async`. Background work (ping, keep-alive, peer
|
|
43
|
+
recovery, restart) runs in tasks the session owns and cancels when it ends
|
|
44
|
+
(`close()` or any other ending); nothing is fire-and-forget. A host may supply its own aiohttp
|
|
45
|
+
session; the library never closes one it did not create.
|
|
46
|
+
|
|
47
|
+
## 5. Errors are typed and scoped
|
|
48
|
+
|
|
49
|
+
A failed edge discovery, a rejected join, a lost gateway connection and a
|
|
50
|
+
malformed message are different exceptions with different recoveries, and
|
|
51
|
+
they say which without a string match. Transient failures never change
|
|
52
|
+
credential or session state. The session reports its lifecycle through one
|
|
53
|
+
state enum and one event callback, not through log lines.
|
|
54
|
+
|
|
55
|
+
## 6. Secrets stay secret
|
|
56
|
+
|
|
57
|
+
Tokens, tickets, credentials, TURN passwords and channel encryption keys never
|
|
58
|
+
appear in log lines, `repr`, or exception messages. Logs may carry a
|
|
59
|
+
fingerprint (short hash) and nothing more.
|
|
60
|
+
|
|
61
|
+
## 7. Wire messages are tolerant, fixtures are exact
|
|
62
|
+
|
|
63
|
+
An unknown message type, an extra field or a missing optional field never
|
|
64
|
+
crashes the session; it is logged once at DEBUG and ignored. Fixtures in
|
|
65
|
+
`tests/fixtures/` are byte-exact recordings (redacted) and tests assert
|
|
66
|
+
against them exactly; when the wire changes, the fixture changes with a
|
|
67
|
+
commit that says why.
|
|
68
|
+
|
|
69
|
+
## 8. Tests are part of the deliverable
|
|
70
|
+
|
|
71
|
+
Nothing merges without tests in the right tier (`docs/testing.md`). Pure
|
|
72
|
+
code (SDP, ORTC, payload building, ICE selection) is tested directly.
|
|
73
|
+
The session is tested against an in-process fake gateway that speaks the
|
|
74
|
+
recorded protocol. Doubles are real objects or hand-written fakes; a bare
|
|
75
|
+
`MagicMock` is not a test double.
|
|
76
|
+
|
|
77
|
+
## 9. Documentation lives outside the code
|
|
78
|
+
|
|
79
|
+
Design is in `docs/`. A comment states a constraint the code cannot express,
|
|
80
|
+
in one or two lines. Every non-obvious choice is a numbered decision; every
|
|
81
|
+
unknown is a numbered open question; every intended change is a backlog
|
|
82
|
+
entry.
|
|
83
|
+
|
|
84
|
+
## 10. The public surface is deliberate
|
|
85
|
+
|
|
86
|
+
`pyagorartc.__all__` lists the supported API. Anything else may change without
|
|
87
|
+
notice. Semver; a breaking change to a listed name bumps the major version.
|
|
88
|
+
Both known hosts (Mammotion, PetKit) have a written migration path in
|
|
89
|
+
`docs/migration.md` before a release that changes the surface.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
1. Read `CONSTITUTION.md`. It is one page and it is not negotiable.
|
|
4
|
+
2. Find the single home for your concern in `docs/architecture.md` §3 before
|
|
5
|
+
writing a line.
|
|
6
|
+
3. New endpoint? Follow the recipe in `docs/architecture.md` §4: model, API
|
|
7
|
+
method, fake-server route, tests, `docs/api/` section.
|
|
8
|
+
4. New non-obvious choice? Add a numbered entry to `docs/decisions.md`. New
|
|
9
|
+
unknown about the API? Add a `Qn` to `docs/open_questions.md`.
|
|
10
|
+
5. Tests in the right tier (`docs/testing.md`), fakes from `tests/unit/_fakes.py`,
|
|
11
|
+
no `MagicMock`, no sleeping for synchronisation.
|
|
12
|
+
6. `uv run pre-commit run --all-files` is green: ruff, ruff-format, ty, unit
|
|
13
|
+
tests.
|
|
14
|
+
7. Commits: imperative subject line, one change per commit, no attribution
|
|
15
|
+
trailers.
|
|
16
|
+
|
|
17
|
+
## Pull request checklist
|
|
18
|
+
|
|
19
|
+
- [ ] The change touches only the places the recipe names.
|
|
20
|
+
- [ ] Every public method has a docstring that says what the signature does not.
|
|
21
|
+
- [ ] No secret can reach a log, a `repr`, or an exception message.
|
|
22
|
+
- [ ] Tests assert behaviour, not call plumbing.
|
|
23
|
+
- [ ] `docs/` updated where the change alters the map, the API, or the rules.
|