aula-connect 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. aula_connect-0.0.1/.git_archival.txt +3 -0
  2. aula_connect-0.0.1/.gitattributes +2 -0
  3. aula_connect-0.0.1/.github/dependabot.yml +24 -0
  4. aula_connect-0.0.1/.github/release.yml +25 -0
  5. aula_connect-0.0.1/.github/rulesets/branch-names.json +26 -0
  6. aula_connect-0.0.1/.github/rulesets/main.json +63 -0
  7. aula_connect-0.0.1/.github/workflows/close-inactive-issues.yml +30 -0
  8. aula_connect-0.0.1/.github/workflows/library-released.yml +32 -0
  9. aula_connect-0.0.1/.github/workflows/release-drafter.yml +28 -0
  10. aula_connect-0.0.1/.github/workflows/release.yml +91 -0
  11. aula_connect-0.0.1/.github/workflows/tests.yml +42 -0
  12. aula_connect-0.0.1/.gitignore +16 -0
  13. aula_connect-0.0.1/CLAUDE.md +152 -0
  14. aula_connect-0.0.1/LICENSE +674 -0
  15. aula_connect-0.0.1/PKG-INFO +64 -0
  16. aula_connect-0.0.1/README.md +32 -0
  17. aula_connect-0.0.1/pyproject.toml +82 -0
  18. aula_connect-0.0.1/requirements-test.txt +11 -0
  19. aula_connect-0.0.1/setup.cfg +4 -0
  20. aula_connect-0.0.1/src/aula_connect/__init__.py +14 -0
  21. aula_connect-0.0.1/src/aula_connect/api/aula_errors.py +15 -0
  22. aula_connect-0.0.1/src/aula_connect/api/aula_proxy_client.py +1368 -0
  23. aula_connect-0.0.1/src/aula_connect/api/const.py +6 -0
  24. aula_connect-0.0.1/src/aula_connect/api/models/aula_birthday_models.py +11 -0
  25. aula_connect-0.0.1/src/aula_connect/api/models/aula_birthday_parser.py +32 -0
  26. aula_connect-0.0.1/src/aula_connect/api/models/aula_calendar_models.py +115 -0
  27. aula_connect-0.0.1/src/aula_connect/api/models/aula_calendar_parser.py +141 -0
  28. aula_connect-0.0.1/src/aula_connect/api/models/aula_easyiq_weekplan_models.py +38 -0
  29. aula_connect-0.0.1/src/aula_connect/api/models/aula_easyiq_weekplan_parser.py +76 -0
  30. aula_connect-0.0.1/src/aula_connect/api/models/aula_message_thread_models.py +61 -0
  31. aula_connect-0.0.1/src/aula_connect/api/models/aula_message_thread_parser.py +114 -0
  32. aula_connect-0.0.1/src/aula_connect/api/models/aula_newsletter_models.py +25 -0
  33. aula_connect-0.0.1/src/aula_connect/api/models/aula_newsletter_parser.py +66 -0
  34. aula_connect-0.0.1/src/aula_connect/api/models/aula_notification_models.py +104 -0
  35. aula_connect-0.0.1/src/aula_connect/api/models/aula_notification_parser.py +195 -0
  36. aula_connect-0.0.1/src/aula_connect/api/models/aula_parser.py +189 -0
  37. aula_connect-0.0.1/src/aula_connect/api/models/aula_profile_models.py +106 -0
  38. aula_connect-0.0.1/src/aula_connect/api/models/aula_profile_parser.py +205 -0
  39. aula_connect-0.0.1/src/aula_connect/api/models/aula_weekly_plan_models.py +40 -0
  40. aula_connect-0.0.1/src/aula_connect/api/models/aula_weekly_plan_parser.py +66 -0
  41. aula_connect-0.0.1/src/aula_connect/api/models/constants.py +104 -0
  42. aula_connect-0.0.1/src/aula_connect/api/models/module.py +142 -0
  43. aula_connect-0.0.1/src/aula_connect/api/module.py +97 -0
  44. aula_connect-0.0.1/src/aula_connect/api/responses/common_data.py +13 -0
  45. aula_connect-0.0.1/src/aula_connect/api/responses/get_birthday_events_for_institutions.py +17 -0
  46. aula_connect-0.0.1/src/aula_connect/api/responses/get_daily_overview_response.py +43 -0
  47. aula_connect-0.0.1/src/aula_connect/api/responses/get_easyiq_weekplan_response.py +14 -0
  48. aula_connect-0.0.1/src/aula_connect/api/responses/get_events_by_profile_ids_and_resource_ids.py +67 -0
  49. aula_connect-0.0.1/src/aula_connect/api/responses/get_message_threads_response.py +48 -0
  50. aula_connect-0.0.1/src/aula_connect/api/responses/get_messages_for_thread_response.py +37 -0
  51. aula_connect-0.0.1/src/aula_connect/api/responses/get_notifications_response.py +34 -0
  52. aula_connect-0.0.1/src/aula_connect/api/responses/get_profile_context_response.py +92 -0
  53. aula_connect-0.0.1/src/aula_connect/api/responses/get_profile_master_data_response.py +23 -0
  54. aula_connect-0.0.1/src/aula_connect/api/responses/get_profiles_by_login_response.py +34 -0
  55. aula_connect-0.0.1/src/aula_connect/api/responses/get_weekly_newsletter_response.py +18 -0
  56. aula_connect-0.0.1/src/aula_connect/api/responses/get_weekly_plans_response.py +23 -0
  57. aula_connect-0.0.1/src/aula_connect/api/utils/list_utils.py +7 -0
  58. aula_connect-0.0.1/src/aula_connect/client.py +224 -0
  59. aula_connect-0.0.1/src/aula_connect/login/__init__.py +20 -0
  60. aula_connect-0.0.1/src/aula_connect/login/client.py +1732 -0
  61. aula_connect-0.0.1/src/aula_connect/login/exceptions.py +43 -0
  62. aula_connect-0.0.1/src/aula_connect/login/mitid/__init__.py +30 -0
  63. aula_connect-0.0.1/src/aula_connect/login/mitid/browser_client.py +813 -0
  64. aula_connect-0.0.1/src/aula_connect/login/mitid/helpers.py +106 -0
  65. aula_connect-0.0.1/src/aula_connect/login/mitid/login_flows/__init__.py +0 -0
  66. aula_connect-0.0.1/src/aula_connect/login/mitid/login_flows/aula.py +10 -0
  67. aula_connect-0.0.1/src/aula_connect/login/mitid/srp.py +158 -0
  68. aula_connect-0.0.1/src/aula_connect/py.typed +0 -0
  69. aula_connect-0.0.1/src/aula_connect.egg-info/PKG-INFO +64 -0
  70. aula_connect-0.0.1/src/aula_connect.egg-info/SOURCES.txt +80 -0
  71. aula_connect-0.0.1/src/aula_connect.egg-info/dependency_links.txt +1 -0
  72. aula_connect-0.0.1/src/aula_connect.egg-info/requires.txt +5 -0
  73. aula_connect-0.0.1/src/aula_connect.egg-info/scm_file_list.json +77 -0
  74. aula_connect-0.0.1/src/aula_connect.egg-info/scm_version.json +8 -0
  75. aula_connect-0.0.1/src/aula_connect.egg-info/top_level.txt +1 -0
  76. aula_connect-0.0.1/tests/api/fixtures/easyiq_weekplan_response.json +28 -0
  77. aula_connect-0.0.1/tests/api/test_easyiq_weekplan.py +89 -0
  78. aula_connect-0.0.1/tests/api/test_error_handling.py +1172 -0
  79. aula_connect-0.0.1/tests/api/test_messages.py +43 -0
  80. aula_connect-0.0.1/tests/api/test_newsletter.py +172 -0
  81. aula_connect-0.0.1/tests/api/test_notifications.py +98 -0
  82. aula_connect-0.0.1/tests/api/test_parser.py +111 -0
@@ -0,0 +1,3 @@
1
+ node: $Format:%H$
2
+ node-date: $Format:%cI$
3
+ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
@@ -0,0 +1,2 @@
1
+ # GitHub's source downloads carry the version setuptools-scm needs, from this file.
2
+ .git_archival.txt export-subst
@@ -0,0 +1,24 @@
1
+ # Proposes updates to the pinned actions and test tools as pull requests, which the tests then
2
+ # check before anything is merged.
3
+ #
4
+ # Shared by every library of the owner, from HairingX/workflows; this file is a copy of its
5
+ # template and is checked to stay so.
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: github-actions
9
+ directory: /
10
+ schedule:
11
+ interval: weekly
12
+ labels:
13
+ - dependencies
14
+ assignees:
15
+ - HairingX
16
+
17
+ - package-ecosystem: pip
18
+ directory: /
19
+ schedule:
20
+ interval: weekly
21
+ labels:
22
+ - dependencies
23
+ assignees:
24
+ - HairingX
@@ -0,0 +1,25 @@
1
+ # How GitHub groups a release's notes: each pull request by its labels.
2
+ changelog:
3
+ # The notes are for users: what is changed for them, not work on the repository itself.
4
+ exclude:
5
+ labels:
6
+ - skip-changelog
7
+ - chore
8
+ - documentation
9
+ - dependencies
10
+ categories:
11
+ - title: 🛠 Breaking Changes
12
+ labels:
13
+ - breaking-change
14
+ - title: 🚀 Features
15
+ labels:
16
+ - feature request
17
+ - enhancement
18
+ - title: 🐛 Bug Fixes
19
+ labels:
20
+ - bug
21
+ - fix
22
+ - bugfix
23
+ - title: Other Changes
24
+ labels:
25
+ - "*"
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "Branch names",
3
+ "target": "branch",
4
+ "enforcement": "active",
5
+ "conditions": {
6
+ "ref_name": {
7
+ "include": [
8
+ "~ALL"
9
+ ],
10
+ "exclude": [
11
+ "refs/heads/main",
12
+ "refs/heads/feature/**/*",
13
+ "refs/heads/fix/**/*",
14
+ "refs/heads/docs/**/*",
15
+ "refs/heads/chore/**/*",
16
+ "refs/heads/dependabot/**/*"
17
+ ]
18
+ }
19
+ },
20
+ "bypass_actors": [],
21
+ "rules": [
22
+ {
23
+ "type": "creation"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "main: pull requests only",
3
+ "target": "branch",
4
+ "enforcement": "active",
5
+ "conditions": {
6
+ "ref_name": {
7
+ "include": [
8
+ "~DEFAULT_BRANCH"
9
+ ],
10
+ "exclude": []
11
+ }
12
+ },
13
+ "bypass_actors": [
14
+ {
15
+ "actor_id": 5,
16
+ "actor_type": "RepositoryRole",
17
+ "bypass_mode": "pull_request"
18
+ }
19
+ ],
20
+ "rules": [
21
+ {
22
+ "type": "deletion"
23
+ },
24
+ {
25
+ "type": "non_fast_forward"
26
+ },
27
+ {
28
+ "type": "pull_request",
29
+ "parameters": {
30
+ "required_approving_review_count": 0,
31
+ "dismiss_stale_reviews_on_push": false,
32
+ "require_code_owner_review": false,
33
+ "require_last_push_approval": false,
34
+ "required_review_thread_resolution": false
35
+ }
36
+ },
37
+ {
38
+ "type": "required_status_checks",
39
+ "parameters": {
40
+ "strict_required_status_checks_policy": false,
41
+ "do_not_enforce_on_create": false,
42
+ "required_status_checks": [
43
+ {
44
+ "context": "Python 3.12",
45
+ "integration_id": 15368
46
+ },
47
+ {
48
+ "context": "Python 3.13",
49
+ "integration_id": 15368
50
+ },
51
+ {
52
+ "context": "Python 3.14",
53
+ "integration_id": 15368
54
+ },
55
+ {
56
+ "context": "Libraries released and pinned",
57
+ "integration_id": 15368
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,30 @@
1
+ name: Close inactive issues
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ schedule:
6
+ - cron: "30 1 * * *"
7
+
8
+ permissions: {}
9
+
10
+ # The shell given explicitly, so GitHub runs every step with pipefail.
11
+ defaults:
12
+ run:
13
+ shell: bash
14
+
15
+ jobs:
16
+ close-issues:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ issues: write
20
+ pull-requests: write
21
+ steps:
22
+ - uses: actions/stale@v11
23
+ with:
24
+ days-before-issue-stale: 30
25
+ days-before-issue-close: 14
26
+ stale-issue-label: "stale"
27
+ stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
28
+ close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
29
+ days-before-pr-stale: -1
30
+ days-before-pr-close: -1
@@ -0,0 +1,32 @@
1
+ name: Library released
2
+
3
+ # A pull request is merged only once every library developed alongside the package comes from
4
+ # its release, not from a branch of the pull request's name.
5
+ #
6
+ # The workflows and the tools are shared by every library of the owner, from
7
+ # HairingX/workflows; this file is a copy of its template and is checked to stay so.
8
+ on:
9
+ pull_request:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ # The shell given explicitly, so GitHub runs every step with pipefail.
15
+ defaults:
16
+ run:
17
+ shell: bash
18
+
19
+ jobs:
20
+ released:
21
+ name: Libraries released and pinned
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: Check out repository
25
+ uses: actions/checkout@v7
26
+ with:
27
+ persist-credentials: false
28
+
29
+ - name: No library from a branch
30
+ uses: HairingX/workflows/python/actions/libraries-released@v2
31
+ with:
32
+ branch: ${{ github.head_ref }}
@@ -0,0 +1,28 @@
1
+ name: Release Drafter
2
+
3
+ # Keeps a draft release proposing the next version, and labels pull requests by their branch's
4
+ # name. The draft is never published by hand: the Release workflow publishes it.
5
+ #
6
+ # The workflows and the tools are shared by every repository of the owner, from
7
+ # HairingX/workflows; this file is a copy of its template and is checked to stay so.
8
+ on:
9
+ push:
10
+ branches:
11
+ - main
12
+ pull_request:
13
+ types: [opened, reopened, synchronize]
14
+
15
+ permissions: {}
16
+
17
+ # The shell given explicitly, so GitHub runs every step with pipefail.
18
+ defaults:
19
+ run:
20
+ shell: bash
21
+
22
+ jobs:
23
+ drafter:
24
+ name: Release Drafter
25
+ permissions:
26
+ contents: write
27
+ pull-requests: write
28
+ uses: HairingX/workflows/.github/workflows/release-drafter.yml@v2
@@ -0,0 +1,91 @@
1
+ name: Release
2
+
3
+ # Run this from the Actions tab, on main, once the maintainer has approved the draft release and
4
+ # its version. Never publish the draft release by hand: the package takes its version from the
5
+ # tag, and only this workflow tags a tested commit.
6
+ #
7
+ # The file name matters: PyPI trusted publishing is bound to this repository, this workflow file
8
+ # and the pypi environment, and takes no reusable workflow, so the upload runs here.
9
+ #
10
+ # The workflows and the tools are shared by every library of the owner, from
11
+ # HairingX/workflows; this file is a copy of its template and is checked to stay so.
12
+ on:
13
+ workflow_dispatch:
14
+ inputs:
15
+ release:
16
+ description: >-
17
+ What to release. The version is worked out from the tags, PyPI and the draft release:
18
+ the next release candidate, or the final release that ends the series.
19
+ required: true
20
+ type: choice
21
+ options:
22
+ - release candidate
23
+ - final release
24
+ default: release candidate
25
+ version:
26
+ description: >-
27
+ Override, only for a new major version, which is never worked out, or when the version
28
+ worked out is wrong: canonical PEP 440 without a v, such as 1.0.0 or 1.0.0rc1, of the
29
+ kind chosen above. It must still be higher than every version tagged or on PyPI.
30
+ required: false
31
+ type: string
32
+
33
+ permissions: {}
34
+
35
+ # GitHub keeps one waiting run per group: a third dispatch cancels the one waiting.
36
+ concurrency:
37
+ group: release
38
+ cancel-in-progress: false
39
+
40
+ # The shell given explicitly, so GitHub runs every step with pipefail.
41
+ defaults:
42
+ run:
43
+ shell: bash
44
+
45
+ jobs:
46
+ release:
47
+ name: Release
48
+ permissions:
49
+ contents: write
50
+ pull-requests: read
51
+ actions: read
52
+ uses: HairingX/workflows/.github/workflows/python-release.yml@v2
53
+ with:
54
+ release: ${{ inputs.release }}
55
+ version: ${{ inputs.version }}
56
+
57
+ pypi:
58
+ name: Publish to PyPI
59
+ needs: release
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ # Mandatory for trusted publishing.
63
+ id-token: write
64
+ environment:
65
+ name: pypi
66
+ url: https://pypi.org/p/${{ needs.release.outputs.package }}
67
+ steps:
68
+ - name: Fetch the distributions
69
+ uses: actions/download-artifact@v8
70
+ with:
71
+ name: dist
72
+ path: dist/
73
+
74
+ # A re-run finds the version it uploaded before.
75
+ - name: Publish
76
+ uses: pypa/gh-action-pypi-publish@release/v1
77
+ with:
78
+ skip-existing: true
79
+
80
+ github-release:
81
+ name: GitHub release
82
+ needs: [release, pypi]
83
+ permissions:
84
+ contents: write
85
+ uses: HairingX/workflows/.github/workflows/python-github-release.yml@v2
86
+ with:
87
+ tag: ${{ needs.release.outputs.tag }}
88
+ prerelease: ${{ needs.release.outputs.prerelease }}
89
+ previous_tag: ${{ needs.release.outputs.previous_tag }}
90
+ draft_id: ${{ needs.release.outputs.draft_id }}
91
+ draft_body: ${{ needs.release.outputs.draft_body }}
@@ -0,0 +1,42 @@
1
+ name: Tests
2
+
3
+ # The workflows and the tools are shared by every library of the owner, from
4
+ # HairingX/workflows; this file is a copy of its template and is checked to stay so.
5
+ on:
6
+ push:
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: tests-${{ github.event_name }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ # The shell given explicitly, so GitHub runs every step with pipefail.
18
+ defaults:
19
+ run:
20
+ shell: bash
21
+
22
+ jobs:
23
+ test:
24
+ name: Python ${{ matrix.python-version }}
25
+ runs-on: ubuntu-latest
26
+ strategy:
27
+ fail-fast: false
28
+ matrix:
29
+ python-version: ["3.12", "3.13", "3.14"]
30
+ steps:
31
+ - name: Check out repository, with every tag
32
+ uses: actions/checkout@v7
33
+ with:
34
+ persist-credentials: false
35
+ # setuptools-scm works the package's version out from the tags.
36
+ fetch-depth: 0
37
+
38
+ - name: Tests and type check
39
+ uses: HairingX/workflows/python/actions/tests@v2
40
+ with:
41
+ python-version: ${{ matrix.python-version }}
42
+ branch: ${{ github.head_ref || github.ref_name }}
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ build/
4
+ dist/
5
+ .venv/
6
+ .pytest_cache/
7
+ .coverage
8
+ .idea/
9
+ .DS_Store
10
+ .claude/worktrees/
11
+ mysecrets.py
12
+
13
+ # Responses captured from a real Aula account hold children's personal data: they stay on the
14
+ # machine they were captured on. Only fixtures made up for the tests are tracked.
15
+ tests/api/fixtures/*
16
+ !tests/api/fixtures/easyiq_weekplan_response.json
@@ -0,0 +1,152 @@
1
+ # Conventions for this repository
2
+
3
+ A Python library for the Danish school platform Aula: it logs in through MitID and reads the
4
+ children's profiles, daily overviews, calendars, messages, notifications, weekly plans and
5
+ newsletters into typed models. The Home Assistant integration `ha_aula_connect` is built on it.
6
+
7
+ ## This library
8
+
9
+ - Responses captured from a real Aula account hold children's personal data. They stay on the
10
+ machine they were captured on: `.gitignore` keeps `tests/api/fixtures/` out of git but for
11
+ the fixtures made up for the tests, and tests that need a captured response skip without it.
12
+ - MitID and Aula credentials and tokens are never logged.
13
+ - It imports nothing from Home Assistant. Aula sends some times as UTC that are the
14
+ institution's local time; `set_time_zone` names that time zone, and the system's is taken
15
+ without it.
16
+ - `aula_connect.login` is the MitID login, `aula_connect.api` the Aula API client with its
17
+ models and parsers, and `aula_connect.AulaClient` the two together, with the tokens kept
18
+ fresh.
19
+ - The code is GPL-3.0-or-later, as the Aula integration it comes from; the MitID browser client
20
+ in `aula_connect.login.mitid` is MIT-licensed by its author.
21
+
22
+ <!-- BEGIN HairingX/workflows: shared; edit it there -->
23
+ ## Shared by every library
24
+
25
+ This section, the workflows and the release tools are the same in every Python library of the
26
+ owner. They come from `HairingX/workflows`: the workflows in `.github/` are copies of its
27
+ template, which call its reusable workflows and actions at `@v2`, and this section is a copy of
28
+ its `python/template/CLAUDE.md`. The tests check that the copies agree; a change to any of them is
29
+ made there, released, and synced into every repository with its
30
+ `common/tools/template.py sync python`. What is this repository's own is above this section.
31
+
32
+ ## Comments and docstrings
33
+
34
+ Based on PEP 8 (Comments), PEP 257 (Docstring Conventions) and the Google Python Style Guide
35
+ (3.8 Comments and Docstrings).
36
+
37
+ **Docstrings describe the unit's own contract, and nothing else.**
38
+ - Every public module, class, function and method has one.
39
+ - The first line is a one-sentence summary. Add more only when a caller needs it: arguments,
40
+ return value, exceptions raised, side effects, invariants.
41
+ - Use Google style sections (`Args:`, `Returns:`, `Raises:`) only when they say something the
42
+ signature and type hints do not. Never repeat types; the hints carry them.
43
+ - Describe what the unit does, not who calls it, how other classes use its data, or where its
44
+ result ends up. That knowledge belongs to those other units and goes stale here.
45
+
46
+ **Comments explain why, never what.**
47
+ - Write one only where the code cannot say it: a protocol quirk, a non-obvious constraint, the
48
+ reason for an order of operations.
49
+ - No history: no "used to", "previously", "the old code", "this was a bug". Version control
50
+ holds history; a regression test's name holds the reason it exists.
51
+ - No references to other files, classes, line numbers or design-document sections. They rot the
52
+ moment the other side changes.
53
+ - No commented-out code. No TODO without an issue reference.
54
+
55
+ **Keep them short.** A comment longer than a few lines is design rationale; it belongs in
56
+ `docs/`, where it can cover several modules at once.
57
+
58
+ **Tests.** The test name states the behaviour. Add a docstring only when the reason is not
59
+ obvious from the name - one or two lines, no narrative.
60
+
61
+ ## Conventions
62
+
63
+ - All documentation, comments and commit messages are in English.
64
+ - The package lives in `src/<package>/`, ships its types (`py.typed`), and supports Python 3.12
65
+ and later (`requires-python = ">=3.12"`); the tests run on 3.12, 3.13 and 3.14.
66
+ - pyright strict must report 0 errors (`pyright`); the configuration is in `pyproject.toml`.
67
+ No `# type: ignore` and no rule turned off.
68
+ - The tests are pytest, in `tests/`, and cover every line of the package; the build fails below
69
+ 100%. They import the installed package, as its users do, never `src.<package>`: a packaging
70
+ mistake then fails a test before a release. Tests that need a real device are marked `live`,
71
+ skip themselves without one, and never run in CI.
72
+ - A repository on its way to this standard says so under `[tool.hairingx-workflows]` in
73
+ `pyproject.toml`: `transition` says why, and `coverage` the percentage its tests must reach
74
+ meanwhile; pyright, still strict, may leave the files listed in `[tool.pyright] ignore`
75
+ unchecked. Every run warns while it lasts. Each pull request raises the coverage and takes
76
+ files off the list, never the reverse, until the transition is removed.
77
+ - Every problem found gets a test that fails without the fix.
78
+ - The test tools are pinned with `==` in `requirements-test.txt`, pyright, pytest and
79
+ pytest-cov among them: `pip install -e . -r requirements-test.txt`.
80
+ - No real IP addresses, hostnames, serial numbers, email addresses or credentials in tracked
81
+ files; real values live in a gitignored file or in environment variables. Examples use the
82
+ documentation ranges, such as 192.0.2.0/24. A credential is never logged.
83
+
84
+ ## Releasing
85
+
86
+ **The version is the release's tag, and the draft release is never published by hand.** The
87
+ package has no version in a file: setuptools-scm works it out from the tags when it is built, so a
88
+ checkout between releases says a development version such as `0.2.1.dev3+g1a2b3c4`.
89
+
90
+ 1. Merge pull requests into `main`. Release Drafter keeps a draft release proposing the next
91
+ version: a minor bump for the label `breaking-change` or `minor`, a patch otherwise.
92
+ **A new major version is never worked out**, as a label placed wrongly must not release one;
93
+ type it into Release as the override.
94
+ 2. Wait until the Release Drafter run on `main` has finished. Release refuses a draft that is not
95
+ up to date with `main`.
96
+ 3. Show the maintainer the draft's notes and its version, and get explicit approval before
97
+ running Release.
98
+ 4. Run the **Release** workflow from the Actions tab, on `main`, and choose **release
99
+ candidate** or **final release**. It works out the version: the next release candidate of the
100
+ open series (`0.2.0rc1` -> `0.2.0rc2`), or the final release that ends it (`0.2.0`); a draft
101
+ naming a higher version starts a new series. Type a version only for a new major, or to
102
+ override one worked out wrongly: canonical PEP 440, of the kind chosen.
103
+
104
+ Release refuses any version not higher than every version tagged or on PyPI, yanked ones
105
+ included, and a version with no pull request merged since the release before it - for a final
106
+ release, the final release before it. It runs the tests on the exact commit it releases, tags
107
+ that commit, builds, checks that the built package says the version, and only then pushes the
108
+ tag - nothing is pushed to `main`. Then PyPI, then the GitHub release with the distributions,
109
+ marked as a pre-release where it is one.
110
+
111
+ A release candidate's notes are written by GitHub from the pull requests merged since the
112
+ previous release candidate or final release, grouped by label as `.github/release.yml` says, and
113
+ the draft is kept. A final release's notes are the draft's - the first final release says only
114
+ that it is the initial release - and the final release deletes the draft. To change what a
115
+ release says, change the pull requests: the title is the line, the labels decide the section.
116
+
117
+ If publishing fails part-way, **Re-run failed jobs** resumes it: the tag, PyPI and the GitHub
118
+ release each accept what an earlier attempt already did.
119
+
120
+ PyPI trusted publishing is bound to this repository, the workflow file `release.yml` and the
121
+ `pypi` environment: renaming either breaks the upload. Actions are referred to by their major
122
+ version; Dependabot proposes a new major, and updates to the test tools, as pull requests.
123
+
124
+ ## Libraries developed together
125
+
126
+ A library this one depends on and develops alongside is listed, in install order, under
127
+ `[tool.hairingx-workflows] libraries` in `pyproject.toml`. A development branch is tested with
128
+ that library's branch of the same name, where it has one, over its release; the check
129
+ "Libraries released and pinned" then fails until the library is released, its version required
130
+ in `pyproject.toml`, and its branch deleted. **Release a library first**, then require its
131
+ version here.
132
+
133
+ ## Branches and pull requests
134
+
135
+ - Every change goes through a branch and a pull request: `main` takes no push. The ruleset in
136
+ `.github/rulesets/main.json` enforces it, with the checks "Python 3.12", "Python 3.13",
137
+ "Python 3.14" and "Libraries released and pinned" required; its only bypass is the owner's,
138
+ and only for merging a pull request. A change to what a user gets - the library's code or
139
+ documentation - comes with its tests.
140
+ - Name every branch by the kind of change, as the release draft's labels follow the name;
141
+ `.github/rulesets/branch-names.json` refuses any other name. `feature/<what>` is labelled
142
+ `feature request`, `fix/<what>` is labelled `bug`, `docs/<what>` is labelled `documentation`,
143
+ and `chore/<what>` - CI, tooling, anything that stays in the repository - is labelled `chore`.
144
+ The release notes list features and fixes; documentation, chores and Dependabot's
145
+ `dependencies` are left out. A change to what a user installs, such as a new dependency, is a
146
+ `fix/` or a `feature/`, never a `chore/`.
147
+ - A pull request's text describes only its own change: what it does and how it was tested. Never
148
+ releases to come, merge order, or other repositories.
149
+ - Put `breaking-change` on a pull request that breaks the API - a `feature/` or `fix/` one, as a
150
+ chore is left out of the notes - and `minor` on one that needs a minor bump; both give a minor
151
+ bump, and a patch is the default. No label gives a major.
152
+ <!-- END HairingX/workflows -->