spgroup 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.
- spgroup-0.1.0/.github/workflows/release.yml +113 -0
- spgroup-0.1.0/.gitignore +10 -0
- spgroup-0.1.0/.postman/resources.yaml +7 -0
- spgroup-0.1.0/AGENTS.md +56 -0
- spgroup-0.1.0/LICENSE +21 -0
- spgroup-0.1.0/MANIFEST.in +2 -0
- spgroup-0.1.0/PKG-INFO +52 -0
- spgroup-0.1.0/README.md +36 -0
- spgroup-0.1.0/docs/adr/adr-two-mode-sdk-vendoring.md +248 -0
- spgroup-0.1.0/docs/api/README.md +88 -0
- spgroup-0.1.0/docs/api/auth-profile-parity-inventory.md +63 -0
- spgroup-0.1.0/docs/api/auth0-api.md +231 -0
- spgroup-0.1.0/docs/api/auth0-flow.md +191 -0
- spgroup-0.1.0/docs/api/content-api.md +64 -0
- spgroup-0.1.0/docs/api/ev-api.md +102 -0
- spgroup-0.1.0/docs/api/frieza-api.md +159 -0
- spgroup-0.1.0/docs/api/greenup-api.md +74 -0
- spgroup-0.1.0/docs/api/jarvis-api.md +318 -0
- spgroup-0.1.0/docs/api/jarvis-parity-inventory.md +59 -0
- spgroup-0.1.0/docs/api/live-e2e.md +132 -0
- spgroup-0.1.0/docs/api/models.md +235 -0
- spgroup-0.1.0/docs/api/network-layer.md +133 -0
- spgroup-0.1.0/docs/api/payment-api.md +268 -0
- spgroup-0.1.0/docs/api/queries.md +198 -0
- spgroup-0.1.0/docs/api/response-schemas.md +306 -0
- spgroup-0.1.0/docs/api/sdk-foundations.md +83 -0
- spgroup-0.1.0/docs/api/skalbox-api.md +133 -0
- spgroup-0.1.0/docs/api/utility-apis.md +85 -0
- spgroup-0.1.0/docs/api/yggdrasil-api.md +219 -0
- spgroup-0.1.0/docs/context-tracker.md +67 -0
- spgroup-0.1.0/docs/plans/sdk-vendoring.md +534 -0
- spgroup-0.1.0/docs/postman-collection.json +543 -0
- spgroup-0.1.0/docs/sdk-parity-roadmap.md +204 -0
- spgroup-0.1.0/docs/sp-mobile-api.postman_collection.json +1246 -0
- spgroup-0.1.0/mise.toml +30 -0
- spgroup-0.1.0/postman/collections/energy-monitoring-api.json +577 -0
- spgroup-0.1.0/postman/collections/sp-mobile-api.json +1395 -0
- spgroup-0.1.0/postman/environments/local.json +91 -0
- spgroup-0.1.0/pyproject.toml +54 -0
- spgroup-0.1.0/resources.yaml +7 -0
- spgroup-0.1.0/setup.cfg +4 -0
- spgroup-0.1.0/src/spgroup/__init__.py +21 -0
- spgroup-0.1.0/src/spgroup/auth.py +14 -0
- spgroup-0.1.0/src/spgroup/client.py +17 -0
- spgroup-0.1.0/src/spgroup/mobile/__init__.py +31 -0
- spgroup-0.1.0/src/spgroup/mobile/auth.py +336 -0
- spgroup-0.1.0/src/spgroup/mobile/constants.py +51 -0
- spgroup-0.1.0/src/spgroup/mobile/errors.py +65 -0
- spgroup-0.1.0/src/spgroup/mobile/jarvis.py +302 -0
- spgroup-0.1.0/src/spgroup/mobile/models.py +26 -0
- spgroup-0.1.0/src/spgroup/mobile/profile.py +178 -0
- spgroup-0.1.0/src/spgroup/mobile/request.py +201 -0
- spgroup-0.1.0/src/spgroup/mobile/resource.py +116 -0
- spgroup-0.1.0/src/spgroup/mobile/session.py +213 -0
- spgroup-0.1.0/src/spgroup/mobile/transport.py +96 -0
- spgroup-0.1.0/src/spgroup/mobile/yggdrasil.py +235 -0
- spgroup-0.1.0/src/spgroup/models.py +16 -0
- spgroup-0.1.0/src/spgroup/py.typed +1 -0
- spgroup-0.1.0/src/spgroup.egg-info/PKG-INFO +52 -0
- spgroup-0.1.0/src/spgroup.egg-info/SOURCES.txt +63 -0
- spgroup-0.1.0/src/spgroup.egg-info/dependency_links.txt +1 -0
- spgroup-0.1.0/src/spgroup.egg-info/requires.txt +1 -0
- spgroup-0.1.0/src/spgroup.egg-info/scm_file_list.json +72 -0
- spgroup-0.1.0/src/spgroup.egg-info/scm_version.json +8 -0
- spgroup-0.1.0/src/spgroup.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
name: Publish spgroup to PyPI
|
|
2
|
+
run-name: ${{ github.actor }} is publishing spgroup to PyPI
|
|
3
|
+
|
|
4
|
+
on: [push]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
build:
|
|
8
|
+
name: Build distribution
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.13"
|
|
17
|
+
- name: Install build tools
|
|
18
|
+
run: |
|
|
19
|
+
python -m pip install build
|
|
20
|
+
python -m pip install -e . pytest
|
|
21
|
+
- name: Build a binary wheel and a source tarball
|
|
22
|
+
run: python -m build
|
|
23
|
+
- name: Verify wheel and sdist integrity
|
|
24
|
+
run: python -m pytest tests/test_sdk_packaging.py -v
|
|
25
|
+
- name: Store the distribution packages
|
|
26
|
+
uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: python-package-distributions
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish-to-testpypi:
|
|
32
|
+
name: Publish to TestPyPI
|
|
33
|
+
if: github.ref_name == 'main' && !startsWith(github.ref, 'refs/tags/')
|
|
34
|
+
needs: [build]
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
|
|
37
|
+
environment:
|
|
38
|
+
name: testpypi
|
|
39
|
+
url: https://test.pypi.org/p/spgroup
|
|
40
|
+
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- name: Download all the dists
|
|
46
|
+
uses: actions/download-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: python-package-distributions
|
|
49
|
+
path: dist/
|
|
50
|
+
- name: Publish distribution to TestPyPI
|
|
51
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
52
|
+
with:
|
|
53
|
+
repository-url: https://test.pypi.org/legacy/
|
|
54
|
+
|
|
55
|
+
publish-to-pypi:
|
|
56
|
+
name: Publish to PyPI
|
|
57
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
58
|
+
needs: [build]
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
|
|
61
|
+
environment:
|
|
62
|
+
name: pypi
|
|
63
|
+
url: https://pypi.org/p/spgroup
|
|
64
|
+
|
|
65
|
+
permissions:
|
|
66
|
+
id-token: write
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- name: Download all the dists
|
|
70
|
+
uses: actions/download-artifact@v4
|
|
71
|
+
with:
|
|
72
|
+
name: python-package-distributions
|
|
73
|
+
path: dist/
|
|
74
|
+
- name: Publish distribution to PyPI
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
76
|
+
|
|
77
|
+
github-release:
|
|
78
|
+
name: Sign and upload to GitHub Release
|
|
79
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
80
|
+
needs: [publish-to-pypi]
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
|
|
83
|
+
permissions:
|
|
84
|
+
contents: write
|
|
85
|
+
id-token: write
|
|
86
|
+
|
|
87
|
+
steps:
|
|
88
|
+
- name: Download all the dists
|
|
89
|
+
uses: actions/download-artifact@v4
|
|
90
|
+
with:
|
|
91
|
+
name: python-package-distributions
|
|
92
|
+
path: dist/
|
|
93
|
+
- name: Sign the dists with Sigstore
|
|
94
|
+
uses: sigstore/gh-action-sigstore-python@v2.1.1
|
|
95
|
+
with:
|
|
96
|
+
inputs: >-
|
|
97
|
+
./dist/*.tar.gz
|
|
98
|
+
./dist/*.whl
|
|
99
|
+
- name: Create GitHub Release
|
|
100
|
+
env:
|
|
101
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
102
|
+
run: >-
|
|
103
|
+
gh release create
|
|
104
|
+
'${{ github.ref_name }}'
|
|
105
|
+
--repo '${{ github.repository }}'
|
|
106
|
+
--notes ""
|
|
107
|
+
- name: Upload artifact signatures to GitHub Release
|
|
108
|
+
env:
|
|
109
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
110
|
+
run: >-
|
|
111
|
+
gh release upload
|
|
112
|
+
'${{ github.ref_name }}' dist/**
|
|
113
|
+
--repo '${{ github.repository }}'
|
spgroup-0.1.0/.gitignore
ADDED
spgroup-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# spgroup SDK — Repository Guidelines
|
|
2
|
+
|
|
3
|
+
Async Python SDK for SP Group (Singapore Power) mobile APIs.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
- `src/spgroup/` — SDK source package
|
|
8
|
+
- `tests/` — Hermetic SDK tests (default `mise run python:test`)
|
|
9
|
+
- `tests/e2e/` — Opt-in live tests against production SP Group endpoints
|
|
10
|
+
- `docs/api/` — Reverse-engineered API reference (APK v15.9.0)
|
|
11
|
+
- `docs/` — SDK-specific docs, roadmaps, plans
|
|
12
|
+
- `prototype/` — Original TypeScript reverse-engineering harness
|
|
13
|
+
- `postman/` — Postman collections for manual API testing
|
|
14
|
+
|
|
15
|
+
The HA integration that consumes this SDK lives in
|
|
16
|
+
[energy-monitoring](../energy-monitoring/).
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
Use `python`, not `python3`, when invoking Python directly.
|
|
21
|
+
|
|
22
|
+
- `mise run python:install` — Install SDK in editable mode with test/lint deps
|
|
23
|
+
- `mise run python:test` — Run hermetic SDK tests
|
|
24
|
+
- `mise run python:test:e2e` — Run opt-in live e2e tests
|
|
25
|
+
- `mise run python:lint` — Run Ruff and mypy
|
|
26
|
+
- `mise run python:build` — Build wheel for publishing
|
|
27
|
+
|
|
28
|
+
## Publishing
|
|
29
|
+
|
|
30
|
+
The primary publishing path is **GitHub Actions CI** using Trusted Publishing.
|
|
31
|
+
Push a `vX.Y.Z` tag to trigger a production PyPI release.
|
|
32
|
+
|
|
33
|
+
### Local/Developer Publishing (TestPyPI dry-run)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mise run python:build
|
|
37
|
+
mise run python:publish:testpypi
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Only after verifying the TestPyPI install:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
mise run python:publish
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Never run `python -m twine upload dist/*` without first confirming
|
|
47
|
+
via TestPyPI.**
|
|
48
|
+
|
|
49
|
+
## Testing
|
|
50
|
+
|
|
51
|
+
Hermetic tests use fake transports and must not call live services.
|
|
52
|
+
Live tests require `--spgroup-live` and a cached mobile session.
|
|
53
|
+
|
|
54
|
+
## Agent Instructions
|
|
55
|
+
|
|
56
|
+
Append discoveries and decisions to `docs/context-tracker.md`.
|
spgroup-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Aw Sheng Xiang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
spgroup-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spgroup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async Python SDK scaffold for SP Group APIs
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/ashe0047/spgroup
|
|
7
|
+
Project-URL: Source, https://github.com/ashe0047/spgroup
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: aiohttp>=3.9
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# spgroup
|
|
18
|
+
|
|
19
|
+
Async Python SDK for SP Group (Singapore Power) utility APIs.
|
|
20
|
+
|
|
21
|
+
Reverse-engineered from the SP Group Android mobile app (v15.9.0).
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install spgroup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import asyncio
|
|
33
|
+
from spgroup import MobileAuthClient, JarvisClient
|
|
34
|
+
|
|
35
|
+
async def main():
|
|
36
|
+
auth = MobileAuthClient(session=None)
|
|
37
|
+
print(f"Client ID: {auth._client_id}")
|
|
38
|
+
|
|
39
|
+
asyncio.run(main())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
API reference and usage guides are available on
|
|
45
|
+
[GitHub](https://github.com/ashe0047/spgroup).
|
|
46
|
+
|
|
47
|
+
For developers working on the SDK: see `docs/`, `prototype/`, and `postman/`
|
|
48
|
+
in the [GitHub repository](https://github.com/ashe0047/spgroup).
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
spgroup-0.1.0/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# spgroup
|
|
2
|
+
|
|
3
|
+
Async Python SDK for SP Group (Singapore Power) utility APIs.
|
|
4
|
+
|
|
5
|
+
Reverse-engineered from the SP Group Android mobile app (v15.9.0).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install spgroup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import asyncio
|
|
17
|
+
from spgroup import MobileAuthClient, JarvisClient
|
|
18
|
+
|
|
19
|
+
async def main():
|
|
20
|
+
auth = MobileAuthClient(session=None)
|
|
21
|
+
print(f"Client ID: {auth._client_id}")
|
|
22
|
+
|
|
23
|
+
asyncio.run(main())
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Documentation
|
|
27
|
+
|
|
28
|
+
API reference and usage guides are available on
|
|
29
|
+
[GitHub](https://github.com/ashe0047/spgroup).
|
|
30
|
+
|
|
31
|
+
For developers working on the SDK: see `docs/`, `prototype/`, and `postman/`
|
|
32
|
+
in the [GitHub repository](https://github.com/ashe0047/spgroup).
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# ADR: Two-Mode SDK Vendoring — Dev Vendor vs. Production Pip
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Revised — 2026-07-17 (replaced try/except fallback with unconditional vendor path)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The SP Group HA custom integration depends on a reusable Python SDK
|
|
10
|
+
(`../python-packages/spgroup/src/spgroup/`) that provides Auth0 authentication,
|
|
11
|
+
SP Group API clients (Jarvis, Yggdrasil, Profile), and mobile session management.
|
|
12
|
+
|
|
13
|
+
The SDK lives in its own repo at `../python-packages/spgroup/`. During
|
|
14
|
+
development, the vendored copy at `custom_components/sp_group/vendor/spgroup/`
|
|
15
|
+
provides the SDK. For production, `manifest.json` declares
|
|
16
|
+
`"requirements": ["spgroup>=0.1.0"]` and HA installs the SDK from PyPI.
|
|
17
|
+
|
|
18
|
+
The integration has two distinct deployment scenarios:
|
|
19
|
+
|
|
20
|
+
| Scenario | SDK source | Import path | Works today? |
|
|
21
|
+
|----------|-----------|-------------|--------------|
|
|
22
|
+
| Development (local checkout) | Vendored copy in vendor/spgroup/ | `from spgroup.mobile import ...` | Yes |
|
|
23
|
+
| Production (HACS / PyPI) | Manifest requirements → pip install | `from spgroup.mobile import ...` | Yes |
|
|
24
|
+
|
|
25
|
+
The core challenge is making `from spgroup.mobile import ...` resolve correctly
|
|
26
|
+
in both modes without changing the import statement.
|
|
27
|
+
|
|
28
|
+
### What the authoritative docs say
|
|
29
|
+
|
|
30
|
+
**Python Language Reference (import system):**
|
|
31
|
+
`sys.path` is a list of directories searched for modules. Programs can modify
|
|
32
|
+
it at runtime. Inserting a directory at position 0 makes it the first search
|
|
33
|
+
location. A non-existent path in `sys.path` is silently ignored during import
|
|
34
|
+
resolution.
|
|
35
|
+
|
|
36
|
+
**HA Developer Docs (manifest requirements):**
|
|
37
|
+
The `requirements` field in `manifest.json` is the official HA mechanism for
|
|
38
|
+
declaring pip dependencies. HA installs them automatically at startup.
|
|
39
|
+
|
|
40
|
+
**Repository split (2026-07-17):**
|
|
41
|
+
The SDK was extracted from the HA integration repo into its own repository at
|
|
42
|
+
`../python-packages/spgroup/`. The vendored copy is refreshed from this repo.
|
|
43
|
+
|
|
44
|
+
### Constraints
|
|
45
|
+
|
|
46
|
+
1. The import statement must be `from spgroup.mobile import ...` in all modes
|
|
47
|
+
2. Dev workflow must not require pip-installing the SDK — the vendor directory
|
|
48
|
+
provides it directly
|
|
49
|
+
3. Production installs must resolve via `manifest.json` requirements (pip)
|
|
50
|
+
4. The existing `test_packaging_boundary.py` test allows `from spgroup.mobile
|
|
51
|
+
import ...` but blocks bare `import spgroup` and `from spgroup.mobile.auth
|
|
52
|
+
import ...` — the import pattern must not violate this contract
|
|
53
|
+
|
|
54
|
+
### Risks
|
|
55
|
+
|
|
56
|
+
1. **sys.path is process-global**: Adding a directory to `sys.path` affects all
|
|
57
|
+
Python code in the HA process. The name `spgroup` is narrow enough that
|
|
58
|
+
conflicts are unlikely, but it must be documented.
|
|
59
|
+
2. **Stale vendored copy**: The vendored SDK is a snapshot. If the upstream SDK
|
|
60
|
+
is updated, the vendored copy must be manually refreshed:
|
|
61
|
+
`cp -a ../python-packages/spgroup/src/spgroup/* vendor/spgroup/`
|
|
62
|
+
|
|
63
|
+
## Decision
|
|
64
|
+
|
|
65
|
+
**Use an unconditional sys.path insert** in the integration's `__init__.py`.
|
|
66
|
+
The vendor path is always added. In development (local checkout), the vendor
|
|
67
|
+
directory exists on disk and provides the SDK. In production (HACS/PyPI), the
|
|
68
|
+
vendor directory is not shipped and the pip-installed SDK takes over.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from __future__ import annotations
|
|
72
|
+
|
|
73
|
+
from pathlib import Path
|
|
74
|
+
|
|
75
|
+
# Dev mode: vendor directory provides the SDK directly.
|
|
76
|
+
# Production: vendor not shipped; manifest.json installs spgroup from pip.
|
|
77
|
+
import sys
|
|
78
|
+
_VENDOR_PATH = str(Path(__file__).resolve().parent / "vendor")
|
|
79
|
+
if _VENDOR_PATH not in sys.path:
|
|
80
|
+
sys.path.insert(0, _VENDOR_PATH)
|
|
81
|
+
|
|
82
|
+
from spgroup.mobile import JarvisClient, MobileAuthClient, ...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Mode 1: Development (local checkout)
|
|
86
|
+
|
|
87
|
+
The vendored SDK copy lives at `custom_components/sp_group/vendor/spgroup/`.
|
|
88
|
+
When `import spgroup` is attempted:
|
|
89
|
+
|
|
90
|
+
1. `sys.path` includes the vendor directory (unconditionally inserted)
|
|
91
|
+
2. Python finds `spgroup` at `vendor/spgroup/` — returns it
|
|
92
|
+
3. `from spgroup.mobile import ...` resolves to the **vendored copy**
|
|
93
|
+
4. To update the vendored copy: `cp -a ../python-packages/spgroup/src/spgroup/* vendor/spgroup/`
|
|
94
|
+
5. If the SDK is also pip-installed, the vendored copy takes priority
|
|
95
|
+
(sys.path[0] is checked before site-packages)
|
|
96
|
+
|
|
97
|
+
**sys.path is modified once at import time, then cached for the process lifetime.**
|
|
98
|
+
|
|
99
|
+
### Mode 2: Production / HACS (pip-installed SDK)
|
|
100
|
+
|
|
101
|
+
The vendored copy is not shipped with the production package. When
|
|
102
|
+
`import spgroup` is attempted:
|
|
103
|
+
|
|
104
|
+
1. `sys.path` includes the vendor directory (unconditionally inserted, but the
|
|
105
|
+
path does not exist on disk — harmless)
|
|
106
|
+
2. Python does not find `spgroup` at the non-existent vendor path
|
|
107
|
+
3. Python continues searching `sys.path` — finds `spgroup` in site-packages
|
|
108
|
+
(installed via `manifest.json` requirements)
|
|
109
|
+
4. `from spgroup.mobile import ...` resolves to the **pip-installed SDK**
|
|
110
|
+
|
|
111
|
+
Adding a non-existent path to `sys.path` is silently ignored by Python's
|
|
112
|
+
import machinery.
|
|
113
|
+
|
|
114
|
+
### Import path resolution summary
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
sys.path[0] = vendor/ (always inserted)
|
|
118
|
+
│
|
|
119
|
+
├── vendor/spgroup/ exists on disk? ─── Yes ──→ Import from vendored copy
|
|
120
|
+
│ (development)
|
|
121
|
+
│
|
|
122
|
+
└── vendor/spgroup/ does not exist
|
|
123
|
+
│
|
|
124
|
+
└── sys.path search continues
|
|
125
|
+
│
|
|
126
|
+
└── spgroup found in site-packages?
|
|
127
|
+
│
|
|
128
|
+
├── Yes ──→ Import from pip-installed SDK (production)
|
|
129
|
+
│
|
|
130
|
+
└── No ──→ ImportError (misconfigured install)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Comparison to previous approach
|
|
134
|
+
|
|
135
|
+
The previous approach used a try/except fallback pattern:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
try:
|
|
139
|
+
import spgroup # pip/editable install exists
|
|
140
|
+
except ImportError:
|
|
141
|
+
_VENDOR_PATH = ...
|
|
142
|
+
if _VENDOR_PATH not in sys.path:
|
|
143
|
+
sys.path.insert(0, _VENDOR_PATH)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This had dual-mode priority where pip-installed took precedence and the
|
|
147
|
+
vendored copy was a fallback. The unconditional approach inverts and
|
|
148
|
+
simplifies this:
|
|
149
|
+
|
|
150
|
+
| Installation | SDK source |
|
|
151
|
+
|-------------|-----------|
|
|
152
|
+
| Dev checkout (vendor exists on disk) | Vendored copy (sys.path[0]) |
|
|
153
|
+
| Production (vendor not shipped) | Pip-installed SDK (site-packages) |
|
|
154
|
+
|
|
155
|
+
Reasons for the change:
|
|
156
|
+
|
|
157
|
+
- **Clear decision split**: Vendor = dev, pip = production. No ambiguous
|
|
158
|
+
try/except priority.
|
|
159
|
+
- **No import overhead**: The unconditional insert costs ~0.01ms (one sys.path
|
|
160
|
+
mutation) vs ~0.5ms for a failed import + mutation in the try/except approach.
|
|
161
|
+
- **Manifest requirements now declared**: The manifest explicitly requires
|
|
162
|
+
`spgroup>=0.1.0`, so production installs fail fast if the SDK is missing.
|
|
163
|
+
- **SDK in its own repo**: After the repo split, the vendored copy is a
|
|
164
|
+
deliberate snapshot, refreshed from `../python-packages/spgroup/`.
|
|
165
|
+
|
|
166
|
+
### Implementation rules
|
|
167
|
+
|
|
168
|
+
1. **Self-containment**: The vendored SDK lives at
|
|
169
|
+
`custom_components/sp_group/vendor/spgroup/` and is a complete, regular
|
|
170
|
+
Python package with `__init__.py` files, not a namespace package.
|
|
171
|
+
|
|
172
|
+
2. **sys.path hygiene**: Insert only once, using the `if path not in
|
|
173
|
+
sys.path` guard. No cleanup needed — the path entry is harmless for the
|
|
174
|
+
process lifetime.
|
|
175
|
+
|
|
176
|
+
3. **Test compatibility**: The `test_packaging_boundary.py` test enforces that
|
|
177
|
+
HA code must not use `import spgroup` (bare top-level) or
|
|
178
|
+
`from spgroup.mobile.auth import ...` (deep submodule). The allowed
|
|
179
|
+
`from spgroup.mobile import ...` pattern matches our usage.
|
|
180
|
+
|
|
181
|
+
4. **No import changes**: The same `from spgroup.mobile import ...` statement
|
|
182
|
+
works in both modes. No `from .vendor.spgroup.mobile import ...` or other
|
|
183
|
+
path-manipulation imports are needed.
|
|
184
|
+
|
|
185
|
+
5. **Manifest requirements**: `manifest.json` declares
|
|
186
|
+
`"requirements": ["spgroup>=0.1.0"]`. In production, HA installs the SDK
|
|
187
|
+
via pip. In development, the vendored copy shadows the pip install.
|
|
188
|
+
|
|
189
|
+
## Consequences
|
|
190
|
+
|
|
191
|
+
### Positive
|
|
192
|
+
|
|
193
|
+
- Single, clean import path everywhere: `from spgroup.mobile import ...`
|
|
194
|
+
- Development workflow does not require pip-installing the SDK
|
|
195
|
+
- Production installs get the SDK via HA's built-in dependency resolution
|
|
196
|
+
- The vendored copy is an explicit dev-mode decision, not a fallback
|
|
197
|
+
- The manifest explicitly declares the dependency
|
|
198
|
+
|
|
199
|
+
### Negative
|
|
200
|
+
|
|
201
|
+
- `sys.path` manipulation is process-global; the `spgroup` name is sufficiently
|
|
202
|
+
unique but must be documented
|
|
203
|
+
- The vendored copy must be manually refreshed from the SDK repo when the
|
|
204
|
+
SDK source changes
|
|
205
|
+
- If the vendored copy and pip-installed SDK are both present, the vendored
|
|
206
|
+
copy wins (sys.path[0] priority) — this is correct for dev but could
|
|
207
|
+
mask version mismatches
|
|
208
|
+
|
|
209
|
+
## Alternatives Considered
|
|
210
|
+
|
|
211
|
+
### try/except fallback (previous approach, now superseded)
|
|
212
|
+
|
|
213
|
+
The initial approach used `try: import spgroup / except ImportError` to
|
|
214
|
+
prefer the pip-installed SDK, falling back to the vendored copy. Superseded
|
|
215
|
+
because:
|
|
216
|
+
- The fallback logic added import overhead for production installs
|
|
217
|
+
- The pip-installed SDK could silently shadow a stale vendored copy during
|
|
218
|
+
development if the developer forgot to uninstall the pip version
|
|
219
|
+
- The manifest had empty requirements — no explicit dependency declaration
|
|
220
|
+
- After the SDK repo split, the unconditional approach makes the dev vs.
|
|
221
|
+
production boundary clearer
|
|
222
|
+
|
|
223
|
+
### Relative imports via `.vendor.spgroup.mobile` (rejected)
|
|
224
|
+
|
|
225
|
+
The initial vendoring implementation used `from .vendor.spgroup.mobile import
|
|
226
|
+
...` — a direct relative import into the vendored copy. This was rejected
|
|
227
|
+
because:
|
|
228
|
+
- Import path differs from the natural `from spgroup.mobile import ...`
|
|
229
|
+
- Tests need a different import path
|
|
230
|
+
- Cannot support the dual-mode (dev vs production) pattern
|
|
231
|
+
- No graceful fallback when the SDK is published to PyPI
|
|
232
|
+
|
|
233
|
+
### Copy SDK files directly into the custom component root (rejected)
|
|
234
|
+
|
|
235
|
+
Placing `spgroup/` as a sibling of `__init__.py` would make it importable as
|
|
236
|
+
`from spgroup.mobile import ...` without `sys.path` manipulation. Rejected
|
|
237
|
+
because:
|
|
238
|
+
- The SDK `spgroup/` dir and the HA integration code (`__init__.py`, `auth.py`,
|
|
239
|
+
etc.) would intermingle in the same directory, creating confusion
|
|
240
|
+
- The `spgroup/` top-level import name would shadow any pip-installed version
|
|
241
|
+
- The `__init__.py` files would be easy to confuse between the SDK and the HA
|
|
242
|
+
integration
|
|
243
|
+
|
|
244
|
+
### Copy-once via a setup script (rejected)
|
|
245
|
+
|
|
246
|
+
A build script that copies the SDK into the custom component at release time.
|
|
247
|
+
Rejected because it adds build complexity and the unconditional path approach
|
|
248
|
+
handles both modes more simply.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# SP Group Mobile API Reference
|
|
2
|
+
|
|
3
|
+
> Reconstructed from `sg.com.singaporepower.spservices` APK v15.9.0
|
|
4
|
+
> Decompiled via JADX from production APK splits (base + arm64_v8a).
|
|
5
|
+
|
|
6
|
+
## Document Structure
|
|
7
|
+
|
|
8
|
+
| File | Covers |
|
|
9
|
+
|------|--------|
|
|
10
|
+
| `auth0-flow.md` | Authentication flow: password-realm login, MFA, token refresh |
|
|
11
|
+
| `auth0-api.md` | Auth0 `/oauth/token` grant types, `/mfa/*` endpoints |
|
|
12
|
+
| `jarvis-api.md` | Utility accounts, usage charts, meter readings, green goals |
|
|
13
|
+
| `skalbox-api.md` | Meter reading, billing preferences, customer consent |
|
|
14
|
+
| `yggdrasil-api.md` | Premises management, sharing, invites |
|
|
15
|
+
| `payment-api.md` | Njord/Nomisma/Sersi payments, subscriptions, UND |
|
|
16
|
+
| `ev-api.md` | EV charging: locations, sessions, subscriptions |
|
|
17
|
+
| `greenup-api.md` | GreenUp quests, OneUp GraphQL, advocado rewards |
|
|
18
|
+
| `frieza-api.md` | Tengah cooling, FCU pairing, appointments |
|
|
19
|
+
| `content-api.md` | Magnolia banners/themes, CMS, system status |
|
|
20
|
+
| `utility-apis.md` | Paladin PPMS, Titan EMA, Mera, Halp, minor APIs |
|
|
21
|
+
| `models.md` | Data models: Credentials, JWT, Premise, Accounts, Charts |
|
|
22
|
+
| `network-layer.md` | Retrofit builders, interceptor chain, error handling |
|
|
23
|
+
| `queries.md` | Full GraphQL query/mutation strings from the APK |
|
|
24
|
+
|
|
25
|
+
## Base URLs
|
|
26
|
+
|
|
27
|
+
| Context | Base URL |
|
|
28
|
+
|---------|----------|
|
|
29
|
+
| Auth0 (mobile) | `https://identity.spdigital.sg` |
|
|
30
|
+
| Authenticated MuleSoft | `https://b2c.api.spdigital.sg` |
|
|
31
|
+
| Public MuleSoft | `https://public.api.spdigital.sg` |
|
|
32
|
+
| Mera API | `https://mera.api.spdigital.sg` |
|
|
33
|
+
| SPC-HC Gateway | `https://sphc-gateway.api.spdigital.sg` |
|
|
34
|
+
|
|
35
|
+
## Authentication Headers
|
|
36
|
+
|
|
37
|
+
All authenticated requests to `b2c.api.spdigital.sg` include:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Authorization: Bearer <access_token>
|
|
41
|
+
X-id-token: <id_token>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Response Wrapper
|
|
45
|
+
|
|
46
|
+
Most endpoints return a `ResourceV2<T>` envelope:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"data": { ... },
|
|
51
|
+
"error": { "code": "ERROR_CODE", "message": "..." }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Domain Glossary
|
|
56
|
+
|
|
57
|
+
| Term | Meaning |
|
|
58
|
+
|------|---------|
|
|
59
|
+
| Premise | Physical address with utility services |
|
|
60
|
+
| BP Number | Billing Party number |
|
|
61
|
+
| MSSL | Meter Serial Number |
|
|
62
|
+
| EBS | Enterprise Billing System reference |
|
|
63
|
+
| PPMS | Pre-Payment Metering System |
|
|
64
|
+
| FCU | Fan Coil Unit (Tengah cooling) |
|
|
65
|
+
| UND | UniDollar payment token |
|
|
66
|
+
| EVSE | Electric Vehicle Supply Equipment |
|
|
67
|
+
| CPO | Charge Point Operator |
|
|
68
|
+
| YCS | Yggdrasil Core Services |
|
|
69
|
+
| OOB | Out-of-Band (SMS MFA) |
|
|
70
|
+
| AMI | Advanced Metering Infrastructure |
|
|
71
|
+
| SMRD | Self-Meter Reading Data |
|
|
72
|
+
|
|
73
|
+
## Service-to-Domain Mapping
|
|
74
|
+
|
|
75
|
+
| Service | Backend | Domain |
|
|
76
|
+
|---------|---------|--------|
|
|
77
|
+
| Jarvis | MuleSoft (b2c) | Utility accounts, charts, meters |
|
|
78
|
+
| Skalbox | MuleSoft (b2c + public) | Meter reading, billing |
|
|
79
|
+
| Yggdrasil | MuleSoft (b2c) | Premises, sharing |
|
|
80
|
+
| Njord | MuleSoft (b2c) | Payments, payables |
|
|
81
|
+
| Nomisma | MuleSoft (b2c) | EV subscriptions |
|
|
82
|
+
| EVA | MuleSoft (b2c) | EV charging |
|
|
83
|
+
| Frosty | MuleSoft (b2c) | Tengah cooling |
|
|
84
|
+
| Frieza | MuleSoft (b2c) | Servicing, warranty |
|
|
85
|
+
| OneUp | MuleSoft (b2c + public) | GreenUp quests |
|
|
86
|
+
| Paladin | MuleSoft (b2c) | PPMS |
|
|
87
|
+
| Magnolia | Contents CDN | Banners, themes |
|
|
88
|
+
| Sersi | MuleSoft (b2c) | UND authorization |
|