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.
@@ -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
@@ -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 `_`