obi-energy-tracker 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.
- obi_energy_tracker-0.1.0/.gitattributes +1 -0
- obi_energy_tracker-0.1.0/.github/CODEOWNERS +1 -0
- obi_energy_tracker-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +45 -0
- obi_energy_tracker-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- obi_energy_tracker-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- obi_energy_tracker-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- obi_energy_tracker-0.1.0/.github/dependabot.yml +15 -0
- obi_energy_tracker-0.1.0/.github/workflows/ci.yml +65 -0
- obi_energy_tracker-0.1.0/.github/workflows/release.yml +108 -0
- obi_energy_tracker-0.1.0/.gitignore +21 -0
- obi_energy_tracker-0.1.0/CHANGELOG.md +30 -0
- obi_energy_tracker-0.1.0/CODE_OF_CONDUCT.md +84 -0
- obi_energy_tracker-0.1.0/CONTRIBUTING.md +86 -0
- obi_energy_tracker-0.1.0/LICENSE +202 -0
- obi_energy_tracker-0.1.0/NOTICE +5 -0
- obi_energy_tracker-0.1.0/PKG-INFO +196 -0
- obi_energy_tracker-0.1.0/README.md +165 -0
- obi_energy_tracker-0.1.0/SECURITY.md +27 -0
- obi_energy_tracker-0.1.0/pyproject.toml +69 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/__init__.py +61 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/aggregation.py +77 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/auth.py +26 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/client.py +202 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/const.py +63 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/exceptions.py +27 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/models.py +76 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/parser.py +193 -0
- obi_energy_tracker-0.1.0/src/obi_energy_tracker/py.typed +0 -0
- obi_energy_tracker-0.1.0/tests/__init__.py +0 -0
- obi_energy_tracker-0.1.0/tests/conftest.py +235 -0
- obi_energy_tracker-0.1.0/tests/test_aggregation.py +220 -0
- obi_energy_tracker-0.1.0/tests/test_auth.py +13 -0
- obi_energy_tracker-0.1.0/tests/test_client.py +873 -0
- obi_energy_tracker-0.1.0/tests/test_exceptions.py +16 -0
- obi_energy_tracker-0.1.0/tests/test_parser.py +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @FabiNaryOBI @clemenslermen-obi
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something in the library does not behave as documented
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
This is the API client. If the problem is with entities, the config flow or the
|
|
9
|
+
Energy dashboard, report it in the OBI ENERGY TRACKER Home Assistant integration
|
|
10
|
+
repository instead.
|
|
11
|
+
- type: input
|
|
12
|
+
id: version
|
|
13
|
+
attributes:
|
|
14
|
+
label: Library version
|
|
15
|
+
placeholder: 0.1.0
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
18
|
+
- type: input
|
|
19
|
+
id: python
|
|
20
|
+
attributes:
|
|
21
|
+
label: Python version
|
|
22
|
+
placeholder: "3.14.2"
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: what-happened
|
|
27
|
+
attributes:
|
|
28
|
+
label: What happened
|
|
29
|
+
description: What did you expect, and what happened instead?
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
- type: textarea
|
|
33
|
+
id: reproduce
|
|
34
|
+
attributes:
|
|
35
|
+
label: How to reproduce
|
|
36
|
+
description: The smallest snippet that shows the problem.
|
|
37
|
+
render: python
|
|
38
|
+
validations:
|
|
39
|
+
required: true
|
|
40
|
+
- type: textarea
|
|
41
|
+
id: logs
|
|
42
|
+
attributes:
|
|
43
|
+
label: Traceback or log output
|
|
44
|
+
description: Remove access tokens and personal data before pasting.
|
|
45
|
+
render: shell
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Ask for a new endpoint, model field or helper
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What are you trying to do?
|
|
9
|
+
description: The use case, not the implementation.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: proposal
|
|
14
|
+
attributes:
|
|
15
|
+
label: Proposed API
|
|
16
|
+
description: How would you like to call it?
|
|
17
|
+
render: python
|
|
18
|
+
- type: textarea
|
|
19
|
+
id: context
|
|
20
|
+
attributes:
|
|
21
|
+
label: Anything else
|
|
22
|
+
description: Endpoint documentation, payload examples, related issues.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## What does this change?
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences, and the issue it closes. -->
|
|
4
|
+
|
|
5
|
+
## Type of change
|
|
6
|
+
|
|
7
|
+
- [ ] Bug fix (no public API change)
|
|
8
|
+
- [ ] New feature (new public name, backwards compatible)
|
|
9
|
+
- [ ] Breaking change (public name removed, renamed or behaving differently)
|
|
10
|
+
- [ ] Documentation or tooling only
|
|
11
|
+
|
|
12
|
+
## Checklist
|
|
13
|
+
|
|
14
|
+
- [ ] `pytest` passes
|
|
15
|
+
- [ ] `ruff check src tests` and `ruff format --check src tests` pass
|
|
16
|
+
- [ ] `mypy` passes
|
|
17
|
+
- [ ] Tests cover the change
|
|
18
|
+
- [ ] No `homeassistant` import was added
|
|
19
|
+
- [ ] `CHANGELOG.md` has an entry under `## Unreleased`
|
|
20
|
+
- [ ] The public surface in `__init__.py` (`__all__`) is up to date
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
open-pull-requests-limit: 5
|
|
8
|
+
groups:
|
|
9
|
+
dev-dependencies:
|
|
10
|
+
patterns:
|
|
11
|
+
- "*"
|
|
12
|
+
- package-ecosystem: github-actions
|
|
13
|
+
directory: /
|
|
14
|
+
schedule:
|
|
15
|
+
interval: weekly
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
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
|
+
lint:
|
|
18
|
+
name: Lint and type check
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: actions/setup-python@v7
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.14"
|
|
25
|
+
cache: pip
|
|
26
|
+
- run: pip install -e ".[dev]"
|
|
27
|
+
- name: ruff check
|
|
28
|
+
run: ruff check src tests
|
|
29
|
+
- name: ruff format
|
|
30
|
+
run: ruff format --check src tests
|
|
31
|
+
- name: mypy
|
|
32
|
+
run: mypy
|
|
33
|
+
|
|
34
|
+
test:
|
|
35
|
+
name: Test on Python ${{ matrix.python-version }}
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
strategy:
|
|
38
|
+
fail-fast: false
|
|
39
|
+
matrix:
|
|
40
|
+
python-version: ["3.13", "3.14"]
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v7
|
|
43
|
+
- uses: actions/setup-python@v7
|
|
44
|
+
with:
|
|
45
|
+
python-version: ${{ matrix.python-version }}
|
|
46
|
+
cache: pip
|
|
47
|
+
- run: pip install -e ".[dev]"
|
|
48
|
+
- run: pytest --cov --cov-report=term-missing
|
|
49
|
+
|
|
50
|
+
build:
|
|
51
|
+
name: Build distribution
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v7
|
|
55
|
+
- uses: actions/setup-python@v7
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.14"
|
|
58
|
+
- run: pip install build twine
|
|
59
|
+
- run: python -m build
|
|
60
|
+
- name: Check metadata
|
|
61
|
+
run: twine check --strict dist/*
|
|
62
|
+
- uses: actions/upload-artifact@v7
|
|
63
|
+
with:
|
|
64
|
+
name: dist
|
|
65
|
+
path: dist/
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
index:
|
|
7
|
+
description: Package index to publish to
|
|
8
|
+
type: choice
|
|
9
|
+
options:
|
|
10
|
+
- pypi
|
|
11
|
+
- testpypi
|
|
12
|
+
default: pypi
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
name: Build distribution
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
outputs:
|
|
22
|
+
version: ${{ steps.version.outputs.version }}
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
- name: Refuse to release from anything but main
|
|
28
|
+
run: |
|
|
29
|
+
if [ "${{ github.ref }}" != "refs/heads/main" ]; then
|
|
30
|
+
echo "::error::Releases are cut from main only, not from ${{ github.ref }}."
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
- name: Refuse to release without a license
|
|
34
|
+
run: |
|
|
35
|
+
if [ ! -f LICENSE ]; then
|
|
36
|
+
echo "::error::No LICENSE file. An unlicensed package on PyPI may not legally be used by anyone — add the license before releasing."
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
- uses: actions/setup-python@v7
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.14"
|
|
42
|
+
- name: Read version from pyproject.toml
|
|
43
|
+
id: version
|
|
44
|
+
run: |
|
|
45
|
+
version="$(python -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
46
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
47
|
+
echo "Releasing version $version"
|
|
48
|
+
- name: Refuse to release an already tagged version
|
|
49
|
+
if: inputs.index == 'pypi'
|
|
50
|
+
run: |
|
|
51
|
+
if git rev-parse -q --verify "refs/tags/v${{ steps.version.outputs.version }}" >/dev/null; then
|
|
52
|
+
echo "::error::Tag v${{ steps.version.outputs.version }} already exists. Bump the version in pyproject.toml first."
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
- run: pip install build twine
|
|
56
|
+
- run: python -m build
|
|
57
|
+
- name: Check metadata
|
|
58
|
+
run: twine check --strict dist/*
|
|
59
|
+
- uses: actions/upload-artifact@v7
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
|
|
64
|
+
publish:
|
|
65
|
+
name: Publish to ${{ inputs.index }}
|
|
66
|
+
needs: build
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
environment:
|
|
69
|
+
name: ${{ inputs.index }}
|
|
70
|
+
url: ${{ inputs.index == 'pypi' && 'https://pypi.org/project/obi-energy-tracker/' || 'https://test.pypi.org/project/obi-energy-tracker/' }}${{ needs.build.outputs.version }}
|
|
71
|
+
permissions:
|
|
72
|
+
id-token: write
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/download-artifact@v8
|
|
75
|
+
with:
|
|
76
|
+
name: dist
|
|
77
|
+
path: dist/
|
|
78
|
+
- name: Publish to TestPyPI
|
|
79
|
+
if: inputs.index == 'testpypi'
|
|
80
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
81
|
+
with:
|
|
82
|
+
repository-url: https://test.pypi.org/legacy/
|
|
83
|
+
- name: Publish to PyPI
|
|
84
|
+
if: inputs.index == 'pypi'
|
|
85
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
86
|
+
|
|
87
|
+
tag:
|
|
88
|
+
name: Tag and create GitHub release
|
|
89
|
+
needs: [build, publish]
|
|
90
|
+
if: inputs.index == 'pypi'
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
permissions:
|
|
93
|
+
contents: write
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v7
|
|
96
|
+
- uses: actions/download-artifact@v8
|
|
97
|
+
with:
|
|
98
|
+
name: dist
|
|
99
|
+
path: dist/
|
|
100
|
+
- name: Create tag and release
|
|
101
|
+
env:
|
|
102
|
+
GH_TOKEN: ${{ github.token }}
|
|
103
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
104
|
+
run: |
|
|
105
|
+
gh release create "v$VERSION" dist/* \
|
|
106
|
+
--title "v$VERSION" \
|
|
107
|
+
--generate-notes \
|
|
108
|
+
--target "$GITHUB_SHA"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this
|
|
6
|
+
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.1.0 - 2026-09-23
|
|
11
|
+
|
|
12
|
+
First public release. Extracted from the OBI ENERGY TRACKER Home Assistant integration,
|
|
13
|
+
where this code ran as an in-tree module.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `ObiEnergyTrackerApi` — async REST client for the OBI ENERGY TRACKER cloud with one
|
|
18
|
+
method per endpoint: `async_get_bridges`, `async_get_bridge_measures`,
|
|
19
|
+
`async_get_meter_readings`, `async_set_outlet_state`,
|
|
20
|
+
`async_get_bridge_firmware_update`, `async_trigger_bridge_firmware_update`.
|
|
21
|
+
`async_get_bridge_measures` covers both windows: with a `duration` the recent values of
|
|
22
|
+
every device, without one their complete history in a single request.
|
|
23
|
+
- `Bridge`, `Device`, `MeasureRecord` and `FirmwareUpdate` payload models, plus the
|
|
24
|
+
`DeviceKind`, `OutletState`, `Measure`, `OtaStatus` and `ConnectionStrength` enums.
|
|
25
|
+
- `ObiEnergyTrackerError` with the `ObiEnergyTrackerAuthError` and
|
|
26
|
+
`ObiEnergyTrackerDeviceOfflineError` subclasses.
|
|
27
|
+
- `TokenProvider` and `static_token_provider` — authentication is awaited before every
|
|
28
|
+
request, so the caller keeps ownership of the OAuth2 session.
|
|
29
|
+
- `connection_strength_from_rssi` — the backend's own RSSI thresholds.
|
|
30
|
+
- `hourly_buckets` and `cumulative` — hourly and cumulative consumption.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
# Contributor Covenant Code of Conduct
|
|
3
|
+
|
|
4
|
+
## Our Pledge
|
|
5
|
+
|
|
6
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
|
7
|
+
|
|
8
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
|
9
|
+
|
|
10
|
+
## Our Standards
|
|
11
|
+
|
|
12
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
|
13
|
+
|
|
14
|
+
* Demonstrating empathy and kindness toward other people
|
|
15
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
16
|
+
* Giving and gracefully accepting constructive feedback
|
|
17
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
|
18
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
|
19
|
+
|
|
20
|
+
Examples of unacceptable behavior include:
|
|
21
|
+
|
|
22
|
+
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
24
|
+
* Public or private harassment
|
|
25
|
+
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
|
26
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
27
|
+
|
|
28
|
+
## Enforcement Responsibilities
|
|
29
|
+
|
|
30
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
|
31
|
+
|
|
32
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
|
33
|
+
|
|
34
|
+
## Scope
|
|
35
|
+
|
|
36
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
37
|
+
|
|
38
|
+
## Enforcement
|
|
39
|
+
|
|
40
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement, listed in [CODEOWNERS](.github/CODEOWNERS). All complaints will be reviewed and investigated promptly and fairly.
|
|
41
|
+
|
|
42
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
|
43
|
+
|
|
44
|
+
## Enforcement Guidelines
|
|
45
|
+
|
|
46
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
|
47
|
+
|
|
48
|
+
### 1. Correction
|
|
49
|
+
|
|
50
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
|
51
|
+
|
|
52
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
|
53
|
+
|
|
54
|
+
### 2. Warning
|
|
55
|
+
|
|
56
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
|
57
|
+
|
|
58
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
|
59
|
+
|
|
60
|
+
### 3. Temporary Ban
|
|
61
|
+
|
|
62
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
|
63
|
+
|
|
64
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
|
65
|
+
|
|
66
|
+
### 4. Permanent Ban
|
|
67
|
+
|
|
68
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
|
69
|
+
|
|
70
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
|
71
|
+
|
|
72
|
+
## Attribution
|
|
73
|
+
|
|
74
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
75
|
+
|
|
76
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
77
|
+
|
|
78
|
+
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
|
|
79
|
+
|
|
80
|
+
[homepage]: https://www.contributor-covenant.org
|
|
81
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
82
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
83
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
84
|
+
[translations]: https://www.contributor-covenant.org/translations
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for taking the time to contribute. This library is the API layer of the
|
|
4
|
+
[OBI ENERGY TRACKER Home Assistant integration](https://github.com/FabiNaryOBI/ha-obi-energy-tracker),
|
|
5
|
+
so a change here can reach every Home Assistant user of the integration.
|
|
6
|
+
|
|
7
|
+
By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
8
|
+
|
|
9
|
+
## Development setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
python3.14 -m venv .venv
|
|
13
|
+
.venv/bin/pip install -e ".[dev]"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Checks
|
|
17
|
+
|
|
18
|
+
All four have to pass before a pull request can be merged; CI runs exactly these:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
.venv/bin/python -m pytest
|
|
22
|
+
.venv/bin/ruff check src tests
|
|
23
|
+
.venv/bin/ruff format --check src tests
|
|
24
|
+
.venv/bin/mypy
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Tests also run against the oldest supported Python (3.13) in CI.
|
|
28
|
+
|
|
29
|
+
## Ground rules
|
|
30
|
+
|
|
31
|
+
- **No Home Assistant imports.** The library must stay usable without Home Assistant.
|
|
32
|
+
Anything that needs `homeassistant` belongs in the integration repository.
|
|
33
|
+
- **Async only.** No blocking I/O, no `requests`, no thread pools. The caller supplies the
|
|
34
|
+
`aiohttp.ClientSession`; the library never creates or closes one.
|
|
35
|
+
- **Fully typed.** `mypy --strict` is clean and `py.typed` is shipped, so consumers get
|
|
36
|
+
the types. New public functions need annotations, not `Any`.
|
|
37
|
+
- **Unknown values do not escape.** A `state`, `measure` or
|
|
38
|
+
`otaStatus` the library does not know parses to `None` or is dropped. Never pass a raw
|
|
39
|
+
backend string to the caller.
|
|
40
|
+
- **Every request carries an explicit timeout.** A shared session's default is far too
|
|
41
|
+
coarse to rely on.
|
|
42
|
+
- **Tests need no network.** `tests/conftest.py` starts a real local `aiohttp` server
|
|
43
|
+
(`FakeBackend`); queue responses with `backend.respond(...)` and assert on
|
|
44
|
+
`backend.requests`. Do not add an HTTP mocking dependency — `aioresponses` breaks
|
|
45
|
+
whenever `aiohttp` changes its internals.
|
|
46
|
+
- **Keep the public surface small.** `__init__.py`'s `__all__` is the contract; everything
|
|
47
|
+
else is an implementation detail and may change in a patch release.
|
|
48
|
+
|
|
49
|
+
## Pull requests
|
|
50
|
+
|
|
51
|
+
1. Branch off `main` (`feat/…`, `fix/…`, `docs/…`).
|
|
52
|
+
2. Keep the change focused, and add tests for it.
|
|
53
|
+
3. Add an entry under `## Unreleased` in [CHANGELOG.md](CHANGELOG.md).
|
|
54
|
+
4. Open the pull request and fill in the template.
|
|
55
|
+
|
|
56
|
+
## Versioning
|
|
57
|
+
|
|
58
|
+
[Semantic Versioning](https://semver.org/). The Home Assistant integration pins this
|
|
59
|
+
package exactly (`obi-energy-tracker==X.Y.Z` in its `manifest.json`), so any change to the
|
|
60
|
+
public surface has to be reflected in the version number:
|
|
61
|
+
|
|
62
|
+
- **major** — a removed or renamed public name, a changed signature, changed parsing
|
|
63
|
+
semantics
|
|
64
|
+
- **minor** — new endpoints, new public helpers, new model fields
|
|
65
|
+
- **patch** — bug fixes and internals
|
|
66
|
+
|
|
67
|
+
## Releasing
|
|
68
|
+
|
|
69
|
+
Releases are cut manually by the repository owners; nothing is published on a push or a
|
|
70
|
+
merge.
|
|
71
|
+
|
|
72
|
+
The repository is licensed under the Apache License, Version 2.0; the copyright holder is
|
|
73
|
+
OBI Smart Technologies GmbH. Contributions are accepted under the same license (see
|
|
74
|
+
section 5 of the license). Do not remove `LICENSE` or `NOTICE` — the release workflow
|
|
75
|
+
refuses to run without a `LICENSE` file, and both files are shipped in the distribution
|
|
76
|
+
via `license-files` in `pyproject.toml`.
|
|
77
|
+
|
|
78
|
+
1. Bump `version` in `pyproject.toml` and rename the `## Unreleased` heading in
|
|
79
|
+
`CHANGELOG.md` to `## X.Y.Z - YYYY-MM-DD`, leaving a fresh empty `## Unreleased` above
|
|
80
|
+
it.
|
|
81
|
+
2. Merge that to `main`.
|
|
82
|
+
3. Run the **Release** workflow (Actions → Release → *Run workflow*) with `index=testpypi`
|
|
83
|
+
first if the packaging changed, then with `index=pypi`. The workflow only runs on `main`.
|
|
84
|
+
4. The workflow builds the distribution, refuses to continue if `vX.Y.Z` is already
|
|
85
|
+
tagged, waits for the `pypi` environment approval, publishes, and then creates the tag
|
|
86
|
+
and the GitHub release.
|