dompower 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.
- dompower-0.1.0/.github/workflows/ci.yml +44 -0
- dompower-0.1.0/.github/workflows/publish.yml +125 -0
- dompower-0.1.0/.gitignore +95 -0
- dompower-0.1.0/.pre-commit-config.yaml +16 -0
- dompower-0.1.0/CLAUDE.md +87 -0
- dompower-0.1.0/PKG-INFO +416 -0
- dompower-0.1.0/README.md +386 -0
- dompower-0.1.0/docs/auth-flow.md +623 -0
- dompower-0.1.0/dompower/__init__.py +105 -0
- dompower-0.1.0/dompower/__main__.py +1028 -0
- dompower-0.1.0/dompower/auth.py +225 -0
- dompower-0.1.0/dompower/client.py +849 -0
- dompower-0.1.0/dompower/const.py +68 -0
- dompower-0.1.0/dompower/exceptions.py +188 -0
- dompower-0.1.0/dompower/gigya_auth.py +963 -0
- dompower-0.1.0/dompower/models.py +382 -0
- dompower-0.1.0/dompower/py.typed +0 -0
- dompower-0.1.0/pyproject.toml +82 -0
- dompower-0.1.0/tests/__init__.py +1 -0
- dompower-0.1.0/tests/conftest.py +30 -0
- dompower-0.1.0/tests/test_client.py +121 -0
- dompower-0.1.0/tests/test_gigya_auth.py +208 -0
- dompower-0.1.0/uv.lock +918 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: Test Python ${{ matrix.python-version }}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.12", "3.13"]
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout repository
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
pip install -e ".[dev]"
|
|
33
|
+
|
|
34
|
+
- name: Run ruff linting
|
|
35
|
+
run: ruff check .
|
|
36
|
+
|
|
37
|
+
- name: Run ruff formatting check
|
|
38
|
+
run: ruff format --check .
|
|
39
|
+
|
|
40
|
+
- name: Run mypy type checking
|
|
41
|
+
run: mypy dompower
|
|
42
|
+
|
|
43
|
+
- name: Run pytest
|
|
44
|
+
run: pytest tests/ -v --tb=short
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: 'Target PyPI environment'
|
|
10
|
+
required: true
|
|
11
|
+
default: 'testpypi'
|
|
12
|
+
type: choice
|
|
13
|
+
options:
|
|
14
|
+
- testpypi
|
|
15
|
+
- pypi
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: publish-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
permissions: {}
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
test:
|
|
25
|
+
name: Test Python ${{ matrix.python-version }}
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
fail-fast: false
|
|
29
|
+
matrix:
|
|
30
|
+
python-version: ["3.12", "3.13"]
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout repository
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
36
|
+
uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: ${{ matrix.python-version }}
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: |
|
|
42
|
+
python -m pip install --upgrade pip
|
|
43
|
+
pip install -e ".[dev]"
|
|
44
|
+
|
|
45
|
+
- name: Run ruff linting
|
|
46
|
+
run: ruff check .
|
|
47
|
+
|
|
48
|
+
- name: Run ruff formatting check
|
|
49
|
+
run: ruff format --check .
|
|
50
|
+
|
|
51
|
+
- name: Run mypy type checking
|
|
52
|
+
run: mypy dompower
|
|
53
|
+
|
|
54
|
+
- name: Run pytest
|
|
55
|
+
run: pytest tests/ -v
|
|
56
|
+
|
|
57
|
+
build:
|
|
58
|
+
name: Build Distribution
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
needs: test
|
|
61
|
+
steps:
|
|
62
|
+
- name: Checkout repository
|
|
63
|
+
uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
- name: Set up Python
|
|
66
|
+
uses: actions/setup-python@v5
|
|
67
|
+
with:
|
|
68
|
+
python-version: "3.12"
|
|
69
|
+
|
|
70
|
+
- name: Install build dependencies
|
|
71
|
+
run: |
|
|
72
|
+
python -m pip install --upgrade pip
|
|
73
|
+
pip install build
|
|
74
|
+
|
|
75
|
+
- name: Build wheel and sdist
|
|
76
|
+
run: python -m build
|
|
77
|
+
|
|
78
|
+
- name: Upload distribution artifacts
|
|
79
|
+
uses: actions/upload-artifact@v4
|
|
80
|
+
with:
|
|
81
|
+
name: dist
|
|
82
|
+
path: dist/
|
|
83
|
+
retention-days: 5
|
|
84
|
+
|
|
85
|
+
publish-testpypi:
|
|
86
|
+
name: Publish to TestPyPI
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
needs: build
|
|
89
|
+
if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi'
|
|
90
|
+
environment:
|
|
91
|
+
name: testpypi
|
|
92
|
+
url: https://test.pypi.org/p/dompower
|
|
93
|
+
permissions:
|
|
94
|
+
id-token: write
|
|
95
|
+
steps:
|
|
96
|
+
- name: Download distribution artifacts
|
|
97
|
+
uses: actions/download-artifact@v4
|
|
98
|
+
with:
|
|
99
|
+
name: dist
|
|
100
|
+
path: dist/
|
|
101
|
+
|
|
102
|
+
- name: Publish to TestPyPI
|
|
103
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
104
|
+
with:
|
|
105
|
+
repository-url: https://test.pypi.org/legacy/
|
|
106
|
+
|
|
107
|
+
publish-pypi:
|
|
108
|
+
name: Publish to PyPI
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
needs: build
|
|
111
|
+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi')
|
|
112
|
+
environment:
|
|
113
|
+
name: pypi
|
|
114
|
+
url: https://pypi.org/p/dompower
|
|
115
|
+
permissions:
|
|
116
|
+
id-token: write
|
|
117
|
+
steps:
|
|
118
|
+
- name: Download distribution artifacts
|
|
119
|
+
uses: actions/download-artifact@v4
|
|
120
|
+
with:
|
|
121
|
+
name: dist
|
|
122
|
+
path: dist/
|
|
123
|
+
|
|
124
|
+
- name: Publish to PyPI
|
|
125
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
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
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.venv
|
|
56
|
+
env/
|
|
57
|
+
venv/
|
|
58
|
+
ENV/
|
|
59
|
+
env.bak/
|
|
60
|
+
venv.bak/
|
|
61
|
+
|
|
62
|
+
# IDE
|
|
63
|
+
.idea/
|
|
64
|
+
.vscode/
|
|
65
|
+
*.swp
|
|
66
|
+
*.swo
|
|
67
|
+
*~
|
|
68
|
+
|
|
69
|
+
# mypy
|
|
70
|
+
.mypy_cache/
|
|
71
|
+
.dmypy.json
|
|
72
|
+
dmypy.json
|
|
73
|
+
|
|
74
|
+
# ruff
|
|
75
|
+
.ruff_cache/
|
|
76
|
+
|
|
77
|
+
# Token files (sensitive)
|
|
78
|
+
tokens.json
|
|
79
|
+
*.tokens
|
|
80
|
+
|
|
81
|
+
# Cookies and session data
|
|
82
|
+
cookies.txt
|
|
83
|
+
cookies*.txt
|
|
84
|
+
|
|
85
|
+
# Log files with sensitive data
|
|
86
|
+
*-log-flow.txt
|
|
87
|
+
output*.txt
|
|
88
|
+
|
|
89
|
+
# OS
|
|
90
|
+
.DS_Store
|
|
91
|
+
Thumbs.db
|
|
92
|
+
|
|
93
|
+
# Local development
|
|
94
|
+
*.local
|
|
95
|
+
.env.local
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.8.2
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
10
|
+
rev: v1.13.0
|
|
11
|
+
hooks:
|
|
12
|
+
- id: mypy
|
|
13
|
+
files: ^dompower/
|
|
14
|
+
additional_dependencies:
|
|
15
|
+
- aiohttp
|
|
16
|
+
- openpyxl
|
dompower-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
dompower is an async Python client library for the Dominion Energy API. It retrieves 30-minute interval electricity usage data and is designed for integration with Home Assistant.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies (uses uv)
|
|
13
|
+
uv sync --dev
|
|
14
|
+
|
|
15
|
+
# Run tests
|
|
16
|
+
uv run pytest
|
|
17
|
+
|
|
18
|
+
# Run a single test
|
|
19
|
+
uv run pytest tests/test_file.py::test_function_name
|
|
20
|
+
|
|
21
|
+
# Type checking
|
|
22
|
+
uv run mypy dompower
|
|
23
|
+
|
|
24
|
+
# Linting
|
|
25
|
+
uv run ruff check dompower
|
|
26
|
+
|
|
27
|
+
# Format code
|
|
28
|
+
uv run ruff format dompower
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
### Core Components
|
|
34
|
+
|
|
35
|
+
- **`client.py`** - `DompowerClient`: Main API client. Handles authenticated requests, token refresh on 401, and parsing API responses. Entry point for all usage data fetching.
|
|
36
|
+
|
|
37
|
+
- **`auth.py`** - `TokenManager`: Manages access/refresh token lifecycle. Tokens expire every 30 minutes; automatically refreshes and invokes callback to persist new tokens.
|
|
38
|
+
|
|
39
|
+
- **`gigya_auth.py`** - `GigyaAuthenticator`: Handles the multi-step Gigya SSO authentication flow with two-factor authentication (TFA) support. Used for initial login (not token refresh).
|
|
40
|
+
|
|
41
|
+
### Authentication Flow
|
|
42
|
+
|
|
43
|
+
1. Initial auth requires manual browser login (CAPTCHA protected) OR programmatic Gigya flow with TFA
|
|
44
|
+
2. Both `access_token` and `refresh_token` are needed for API calls
|
|
45
|
+
3. Tokens rotate on every refresh (both change) - client notifies via `token_update_callback`
|
|
46
|
+
4. Access tokens expire in 30 minutes; refresh tokens expire after extended inactivity
|
|
47
|
+
|
|
48
|
+
### Data Models (`models.py`)
|
|
49
|
+
|
|
50
|
+
Key frozen dataclasses:
|
|
51
|
+
- `IntervalUsageData` - 30-minute consumption data (timestamp, consumption, unit)
|
|
52
|
+
- `TokenPair` - Access/refresh token pair
|
|
53
|
+
- `BillForecast` / `BillPeriodData` - Billing data with `derived_rate` property for $/kWh
|
|
54
|
+
- `CustomerInfo` / `AccountInfo` / `MeterDevice` - Account hierarchy
|
|
55
|
+
- `GigyaSession` / `TFATarget` - TFA authentication state
|
|
56
|
+
|
|
57
|
+
### Exception Hierarchy (`exceptions.py`)
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
DompowerError
|
|
61
|
+
├── AuthenticationError
|
|
62
|
+
│ ├── InvalidAuthError
|
|
63
|
+
│ ├── TokenExpiredError
|
|
64
|
+
│ ├── BrowserAuthRequiredError
|
|
65
|
+
│ └── GigyaError
|
|
66
|
+
│ ├── InvalidCredentialsError
|
|
67
|
+
│ ├── TFARequiredError
|
|
68
|
+
│ ├── TFAVerificationError
|
|
69
|
+
│ └── TFAExpiredError
|
|
70
|
+
├── CannotConnectError
|
|
71
|
+
└── ApiError
|
|
72
|
+
└── RateLimitError
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Testing
|
|
76
|
+
|
|
77
|
+
- Uses pytest-asyncio with `asyncio_mode = "auto"`
|
|
78
|
+
- Uses aresponses for mocking aiohttp requests
|
|
79
|
+
- Test fixtures in `tests/conftest.py`
|
|
80
|
+
|
|
81
|
+
## Code Style
|
|
82
|
+
|
|
83
|
+
- Python 3.12+ with strict mypy
|
|
84
|
+
- Ruff for linting (includes isort, pycodestyle, flake8-bugbear, flake8-bandit)
|
|
85
|
+
- All public classes/methods have docstrings
|
|
86
|
+
- Async methods prefixed with `async_`
|
|
87
|
+
- Private methods prefixed with `_`
|