python-appie 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.
- python_appie-0.1.0/.github/workflows/ci.yml +43 -0
- python_appie-0.1.0/.github/workflows/docs.yml +44 -0
- python_appie-0.1.0/.github/workflows/pypi.yml +47 -0
- python_appie-0.1.0/.gitignore +8 -0
- python_appie-0.1.0/.pre-commit-config.yaml +28 -0
- python_appie-0.1.0/PKG-INFO +211 -0
- python_appie-0.1.0/README.md +186 -0
- python_appie-0.1.0/docs/authentication.md +21 -0
- python_appie-0.1.0/docs/cli.md +11 -0
- python_appie-0.1.0/docs/development.md +28 -0
- python_appie-0.1.0/docs/getting-started.md +47 -0
- python_appie-0.1.0/docs/index.md +29 -0
- python_appie-0.1.0/docs/lists.md +24 -0
- python_appie-0.1.0/docs/mock-client.md +76 -0
- python_appie-0.1.0/docs/products.md +36 -0
- python_appie-0.1.0/docs/receipts.md +41 -0
- python_appie-0.1.0/mkdocs.yml +29 -0
- python_appie-0.1.0/pyproject.toml +78 -0
- python_appie-0.1.0/src/appie/__init__.py +29 -0
- python_appie-0.1.0/src/appie/auth.py +159 -0
- python_appie-0.1.0/src/appie/client.py +248 -0
- python_appie-0.1.0/src/appie/lists.py +78 -0
- python_appie-0.1.0/src/appie/mock.py +211 -0
- python_appie-0.1.0/src/appie/models.py +82 -0
- python_appie-0.1.0/src/appie/products.py +99 -0
- python_appie-0.1.0/src/appie/receipts.py +106 -0
- python_appie-0.1.0/tests/conftest.py +53 -0
- python_appie-0.1.0/tests/test_auth.py +199 -0
- python_appie-0.1.0/tests/test_client.py +344 -0
- python_appie-0.1.0/tests/test_mock.py +54 -0
- python_appie-0.1.0/tests/test_package.py +17 -0
- python_appie-0.1.0/uv.lock +1209 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
quality:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.11", "3.12"]
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: uv sync --extra dev
|
|
22
|
+
- name: Ruff format check
|
|
23
|
+
run: uv run ruff format --check .
|
|
24
|
+
- name: Ruff lint
|
|
25
|
+
run: uv run ruff check .
|
|
26
|
+
- name: Pyright
|
|
27
|
+
run: uv run pyright
|
|
28
|
+
- name: Pytest
|
|
29
|
+
run: uv run pytest
|
|
30
|
+
|
|
31
|
+
docs:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- uses: astral-sh/setup-uv@v4
|
|
37
|
+
- uses: actions/setup-python@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: "3.11"
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: uv sync --extra dev
|
|
42
|
+
- name: Build docs
|
|
43
|
+
run: uv run mkdocs build --strict
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: pages
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v4
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.11"
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --extra dev
|
|
28
|
+
- name: Build docs
|
|
29
|
+
run: uv run mkdocs build --strict
|
|
30
|
+
- name: Upload pages artifact
|
|
31
|
+
uses: actions/upload-pages-artifact@v3
|
|
32
|
+
with:
|
|
33
|
+
path: site
|
|
34
|
+
|
|
35
|
+
deploy:
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment:
|
|
39
|
+
name: github-pages
|
|
40
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
41
|
+
steps:
|
|
42
|
+
- name: Deploy to GitHub Pages
|
|
43
|
+
id: deployment
|
|
44
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.11"
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync --extra dev
|
|
25
|
+
- name: Build package
|
|
26
|
+
run: uv build
|
|
27
|
+
- name: Upload distributions
|
|
28
|
+
uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: python-appie-dist
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: pypi
|
|
38
|
+
url: https://pypi.org/p/python-appie
|
|
39
|
+
|
|
40
|
+
steps:
|
|
41
|
+
- name: Download distributions
|
|
42
|
+
uses: actions/download-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: python-appie-dist
|
|
45
|
+
path: dist/
|
|
46
|
+
- name: Publish to PyPI
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff-format
|
|
5
|
+
name: ruff format
|
|
6
|
+
entry: uv run ruff format .
|
|
7
|
+
language: system
|
|
8
|
+
pass_filenames: false
|
|
9
|
+
- id: ruff-check
|
|
10
|
+
name: ruff check
|
|
11
|
+
entry: uv run ruff check .
|
|
12
|
+
language: system
|
|
13
|
+
pass_filenames: false
|
|
14
|
+
- id: pyright
|
|
15
|
+
name: pyright
|
|
16
|
+
entry: uv run pyright
|
|
17
|
+
language: system
|
|
18
|
+
pass_filenames: false
|
|
19
|
+
- id: pytest
|
|
20
|
+
name: pytest
|
|
21
|
+
entry: uv run pytest
|
|
22
|
+
language: system
|
|
23
|
+
pass_filenames: false
|
|
24
|
+
- id: mkdocs-build
|
|
25
|
+
name: mkdocs build
|
|
26
|
+
entry: uv run mkdocs build --strict
|
|
27
|
+
language: system
|
|
28
|
+
pass_filenames: false
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-appie
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client for the Albert Heijn API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/tijnschouten/appie
|
|
6
|
+
Project-URL: Documentation, https://tijnschouten.github.io/appie/
|
|
7
|
+
Project-URL: Repository, https://github.com/tijnschouten/appie
|
|
8
|
+
Project-URL: Issues, https://github.com/tijnschouten/appie/issues
|
|
9
|
+
Author: Codex
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: httpx
|
|
12
|
+
Requires-Dist: playwright>=1.52.0
|
|
13
|
+
Requires-Dist: pydantic>=2
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
|
|
16
|
+
Requires-Dist: mkdocs>=1.6; extra == 'dev'
|
|
17
|
+
Requires-Dist: pre-commit>=4.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: pyright>=1.1.390; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
22
|
+
Requires-Dist: respx; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# python-appie
|
|
27
|
+
|
|
28
|
+
`python-appie` is an unofficial async Python client for the Albert Heijn API.
|
|
29
|
+
|
|
30
|
+
Full documentation lives in [`docs/`](/Users/tijnschouten/repos/personal/appie/docs), is built from [`mkdocs.yml`](/Users/tijnschouten/repos/personal/appie/mkdocs.yml), and is intended to be published at [tijnschouten.github.io/appie](https://tijnschouten.github.io/appie/).
|
|
31
|
+
|
|
32
|
+
Releases are intended to publish to PyPI as `python-appie` from version tags via GitHub Actions.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv add python-appie
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install python-appie
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For local development in this repository:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv sync --extra dev
|
|
50
|
+
pre-commit install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
Authenticate once:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv run appie-login
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This opens Chrome for an interactive AH login and captures the OAuth redirect code automatically. If automatic capture cannot start, the CLI falls back to asking for the redirect URL or raw code manually.
|
|
62
|
+
|
|
63
|
+
Then use the client:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import asyncio
|
|
67
|
+
|
|
68
|
+
from appie import AHClient
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def main() -> None:
|
|
72
|
+
async with AHClient() as client:
|
|
73
|
+
products = await client.products.search("melk", limit=3)
|
|
74
|
+
for product in products:
|
|
75
|
+
print(product)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Tokens are stored in `~/.config/appie/tokens.json` and refreshed automatically when they are close to expiring.
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
### Authentication
|
|
86
|
+
|
|
87
|
+
- `appie-login` CLI for browser-based login
|
|
88
|
+
- automatic code capture from the AH redirect flow
|
|
89
|
+
- token persistence in `~/.config/appie/tokens.json`
|
|
90
|
+
- automatic token refresh using the stored refresh token
|
|
91
|
+
|
|
92
|
+
### Products
|
|
93
|
+
|
|
94
|
+
- search products via `client.products.search(query, limit=10)`
|
|
95
|
+
- fetch a single product via `client.products.get(product_id)`
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import asyncio
|
|
101
|
+
|
|
102
|
+
from appie import AHClient
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def main() -> None:
|
|
106
|
+
async with AHClient() as client:
|
|
107
|
+
product = await client.products.get(1525)
|
|
108
|
+
print(product)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
asyncio.run(main())
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Receipts
|
|
115
|
+
|
|
116
|
+
- list in-store POS receipt summaries via `client.receipts.list_all(limit=50)`
|
|
117
|
+
- fetch a receipt with line items via `client.receipts.get_pos_receipt(receipt_id)`
|
|
118
|
+
|
|
119
|
+
Important:
|
|
120
|
+
`list_all()` and `list_pos_receipts()` return receipt summaries. In those results, `products` is intentionally empty.
|
|
121
|
+
To retrieve line items, call `get_pos_receipt()` with a receipt ID from the summary list.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import asyncio
|
|
127
|
+
|
|
128
|
+
from appie import AHClient
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def main() -> None:
|
|
132
|
+
async with AHClient() as client:
|
|
133
|
+
receipts = await client.receipts.list_all(limit=5)
|
|
134
|
+
detailed = await client.receipts.get_pos_receipt(receipts[0].id)
|
|
135
|
+
print(detailed)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
asyncio.run(main())
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Shopping lists
|
|
142
|
+
|
|
143
|
+
- add an item via `client.lists.add_item(description, quantity=1, product_id=None)`
|
|
144
|
+
- use `MockAHClient` for local development and tests without touching AH
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import asyncio
|
|
150
|
+
|
|
151
|
+
from appie import AHClient
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def main() -> None:
|
|
155
|
+
async with AHClient() as client:
|
|
156
|
+
item = await client.lists.add_item("Halfvolle melk", quantity=2)
|
|
157
|
+
print(item)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
asyncio.run(main())
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Current limitation:
|
|
164
|
+
shopping-list add is implemented, but `get_list()`, `remove_item()`, and `clear()` still raise `NotImplementedError` until their live API shape is confirmed.
|
|
165
|
+
|
|
166
|
+
## API overview
|
|
167
|
+
|
|
168
|
+
### Main client
|
|
169
|
+
|
|
170
|
+
- `AHClient()`
|
|
171
|
+
- `MockAHClient()`
|
|
172
|
+
- `await client.login()`
|
|
173
|
+
- `await client.graphql(query, variables=None)`
|
|
174
|
+
|
|
175
|
+
### Auth client
|
|
176
|
+
|
|
177
|
+
- `AHAuthClient.get_anonymous_token()`
|
|
178
|
+
- `AHAuthClient.login_with_code(code)`
|
|
179
|
+
- `AHAuthClient.refresh_token(refresh_token)`
|
|
180
|
+
|
|
181
|
+
### Sub-APIs
|
|
182
|
+
|
|
183
|
+
- `client.products.search(query, limit=10)`
|
|
184
|
+
- `client.products.get(product_id)`
|
|
185
|
+
- `client.receipts.list_pos_receipts(limit=50)`
|
|
186
|
+
- `client.receipts.list_all(limit=50)`
|
|
187
|
+
- `client.receipts.get_pos_receipt(receipt_id)`
|
|
188
|
+
- `client.lists.add_item(description, quantity=1, product_id=None)`
|
|
189
|
+
- `client.lists.get_list()`
|
|
190
|
+
- `client.lists.remove_item(item_id)`
|
|
191
|
+
- `client.lists.clear()`
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
Run checks locally:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
uv run ruff format .
|
|
199
|
+
uv run --extra dev ruff check .
|
|
200
|
+
uv run --extra dev pyright
|
|
201
|
+
uv run --extra dev pytest
|
|
202
|
+
uv run --extra dev mkdocs build --strict
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Notes
|
|
206
|
+
|
|
207
|
+
- This client is unofficial and may break when Albert Heijn changes its backend.
|
|
208
|
+
- Receipt support currently covers in-store POS receipts.
|
|
209
|
+
- Shopping-list support only implements the verified add-item mutation; other operations raise explicit `NotImplementedError` until their GraphQL shape is confirmed.
|
|
210
|
+
- Receipt summaries do not include line items; call `get_pos_receipt()` for a detailed receipt.
|
|
211
|
+
- Endpoint discovery for this package is inspired by [gwillem/appie-go](https://github.com/gwillem/appie-go).
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# python-appie
|
|
2
|
+
|
|
3
|
+
`python-appie` is an unofficial async Python client for the Albert Heijn API.
|
|
4
|
+
|
|
5
|
+
Full documentation lives in [`docs/`](/Users/tijnschouten/repos/personal/appie/docs), is built from [`mkdocs.yml`](/Users/tijnschouten/repos/personal/appie/mkdocs.yml), and is intended to be published at [tijnschouten.github.io/appie](https://tijnschouten.github.io/appie/).
|
|
6
|
+
|
|
7
|
+
Releases are intended to publish to PyPI as `python-appie` from version tags via GitHub Actions.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv add python-appie
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install python-appie
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For local development in this repository:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv sync --extra dev
|
|
25
|
+
pre-commit install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
Authenticate once:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv run appie-login
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This opens Chrome for an interactive AH login and captures the OAuth redirect code automatically. If automatic capture cannot start, the CLI falls back to asking for the redirect URL or raw code manually.
|
|
37
|
+
|
|
38
|
+
Then use the client:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
|
|
43
|
+
from appie import AHClient
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def main() -> None:
|
|
47
|
+
async with AHClient() as client:
|
|
48
|
+
products = await client.products.search("melk", limit=3)
|
|
49
|
+
for product in products:
|
|
50
|
+
print(product)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Tokens are stored in `~/.config/appie/tokens.json` and refreshed automatically when they are close to expiring.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
### Authentication
|
|
61
|
+
|
|
62
|
+
- `appie-login` CLI for browser-based login
|
|
63
|
+
- automatic code capture from the AH redirect flow
|
|
64
|
+
- token persistence in `~/.config/appie/tokens.json`
|
|
65
|
+
- automatic token refresh using the stored refresh token
|
|
66
|
+
|
|
67
|
+
### Products
|
|
68
|
+
|
|
69
|
+
- search products via `client.products.search(query, limit=10)`
|
|
70
|
+
- fetch a single product via `client.products.get(product_id)`
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import asyncio
|
|
76
|
+
|
|
77
|
+
from appie import AHClient
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def main() -> None:
|
|
81
|
+
async with AHClient() as client:
|
|
82
|
+
product = await client.products.get(1525)
|
|
83
|
+
print(product)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Receipts
|
|
90
|
+
|
|
91
|
+
- list in-store POS receipt summaries via `client.receipts.list_all(limit=50)`
|
|
92
|
+
- fetch a receipt with line items via `client.receipts.get_pos_receipt(receipt_id)`
|
|
93
|
+
|
|
94
|
+
Important:
|
|
95
|
+
`list_all()` and `list_pos_receipts()` return receipt summaries. In those results, `products` is intentionally empty.
|
|
96
|
+
To retrieve line items, call `get_pos_receipt()` with a receipt ID from the summary list.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
import asyncio
|
|
102
|
+
|
|
103
|
+
from appie import AHClient
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def main() -> None:
|
|
107
|
+
async with AHClient() as client:
|
|
108
|
+
receipts = await client.receipts.list_all(limit=5)
|
|
109
|
+
detailed = await client.receipts.get_pos_receipt(receipts[0].id)
|
|
110
|
+
print(detailed)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
asyncio.run(main())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Shopping lists
|
|
117
|
+
|
|
118
|
+
- add an item via `client.lists.add_item(description, quantity=1, product_id=None)`
|
|
119
|
+
- use `MockAHClient` for local development and tests without touching AH
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import asyncio
|
|
125
|
+
|
|
126
|
+
from appie import AHClient
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def main() -> None:
|
|
130
|
+
async with AHClient() as client:
|
|
131
|
+
item = await client.lists.add_item("Halfvolle melk", quantity=2)
|
|
132
|
+
print(item)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
asyncio.run(main())
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Current limitation:
|
|
139
|
+
shopping-list add is implemented, but `get_list()`, `remove_item()`, and `clear()` still raise `NotImplementedError` until their live API shape is confirmed.
|
|
140
|
+
|
|
141
|
+
## API overview
|
|
142
|
+
|
|
143
|
+
### Main client
|
|
144
|
+
|
|
145
|
+
- `AHClient()`
|
|
146
|
+
- `MockAHClient()`
|
|
147
|
+
- `await client.login()`
|
|
148
|
+
- `await client.graphql(query, variables=None)`
|
|
149
|
+
|
|
150
|
+
### Auth client
|
|
151
|
+
|
|
152
|
+
- `AHAuthClient.get_anonymous_token()`
|
|
153
|
+
- `AHAuthClient.login_with_code(code)`
|
|
154
|
+
- `AHAuthClient.refresh_token(refresh_token)`
|
|
155
|
+
|
|
156
|
+
### Sub-APIs
|
|
157
|
+
|
|
158
|
+
- `client.products.search(query, limit=10)`
|
|
159
|
+
- `client.products.get(product_id)`
|
|
160
|
+
- `client.receipts.list_pos_receipts(limit=50)`
|
|
161
|
+
- `client.receipts.list_all(limit=50)`
|
|
162
|
+
- `client.receipts.get_pos_receipt(receipt_id)`
|
|
163
|
+
- `client.lists.add_item(description, quantity=1, product_id=None)`
|
|
164
|
+
- `client.lists.get_list()`
|
|
165
|
+
- `client.lists.remove_item(item_id)`
|
|
166
|
+
- `client.lists.clear()`
|
|
167
|
+
|
|
168
|
+
## Development
|
|
169
|
+
|
|
170
|
+
Run checks locally:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
uv run ruff format .
|
|
174
|
+
uv run --extra dev ruff check .
|
|
175
|
+
uv run --extra dev pyright
|
|
176
|
+
uv run --extra dev pytest
|
|
177
|
+
uv run --extra dev mkdocs build --strict
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Notes
|
|
181
|
+
|
|
182
|
+
- This client is unofficial and may break when Albert Heijn changes its backend.
|
|
183
|
+
- Receipt support currently covers in-store POS receipts.
|
|
184
|
+
- Shopping-list support only implements the verified add-item mutation; other operations raise explicit `NotImplementedError` until their GraphQL shape is confirmed.
|
|
185
|
+
- Receipt summaries do not include line items; call `get_pos_receipt()` for a detailed receipt.
|
|
186
|
+
- Endpoint discovery for this package is inspired by [gwillem/appie-go](https://github.com/gwillem/appie-go).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
## Login flow
|
|
4
|
+
|
|
5
|
+
The package uses a browser-assisted login flow exposed through `appie-login`.
|
|
6
|
+
|
|
7
|
+
The CLI:
|
|
8
|
+
|
|
9
|
+
- opens a Chrome window
|
|
10
|
+
- waits for the AH redirect to `appie://login-exit?code=...`
|
|
11
|
+
- exchanges that code for tokens
|
|
12
|
+
- stores tokens in `~/.config/appie/tokens.json`
|
|
13
|
+
|
|
14
|
+
## Token refresh
|
|
15
|
+
|
|
16
|
+
Access tokens are refreshed automatically when they are close to expiry. Under normal usage, you should only need to run `appie-login` again when the stored refresh token is no longer valid.
|
|
17
|
+
|
|
18
|
+
## Notes
|
|
19
|
+
|
|
20
|
+
- This is not an official AH integration.
|
|
21
|
+
- AH may change login requirements at any time.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync --extra dev
|
|
7
|
+
pre-commit install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quality checks
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv run ruff format .
|
|
14
|
+
uv run ruff check .
|
|
15
|
+
uv run pyright
|
|
16
|
+
uv run pytest
|
|
17
|
+
uv run mkdocs build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Pre-commit
|
|
21
|
+
|
|
22
|
+
This repository uses pre-commit hooks for:
|
|
23
|
+
|
|
24
|
+
- `ruff format`
|
|
25
|
+
- `ruff check`
|
|
26
|
+
- `pyright`
|
|
27
|
+
- `pytest`
|
|
28
|
+
- `mkdocs build`
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv add python-appie
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Or:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install python-appie
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For local development in this repository:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv sync --extra dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Login
|
|
22
|
+
|
|
23
|
+
Run:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv run appie-login
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This opens Chrome and captures the AH login redirect code automatically. Tokens are stored in `~/.config/appie/tokens.json`.
|
|
30
|
+
|
|
31
|
+
## First request
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
|
|
36
|
+
from appie import AHClient
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def main() -> None:
|
|
40
|
+
async with AHClient() as client:
|
|
41
|
+
products = await client.products.search("melk", limit=3)
|
|
42
|
+
for product in products:
|
|
43
|
+
print(product)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
asyncio.run(main())
|
|
47
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# python-appie
|
|
2
|
+
|
|
3
|
+
`python-appie` is an unofficial async Python client for the Albert Heijn API.
|
|
4
|
+
|
|
5
|
+
It currently supports:
|
|
6
|
+
|
|
7
|
+
- browser-based login via `appie-login`
|
|
8
|
+
- token persistence and refresh
|
|
9
|
+
- product search and product detail lookup
|
|
10
|
+
- receipt summary listing
|
|
11
|
+
- receipt detail retrieval with line items
|
|
12
|
+
- shopping-list item creation
|
|
13
|
+
|
|
14
|
+
## Current status
|
|
15
|
+
|
|
16
|
+
This package talks to an unofficial API. That means:
|
|
17
|
+
|
|
18
|
+
- the API may change without notice
|
|
19
|
+
- fields and endpoints can break at any time
|
|
20
|
+
- conservative usage is recommended
|
|
21
|
+
|
|
22
|
+
## Read next
|
|
23
|
+
|
|
24
|
+
- [Getting Started](getting-started.md)
|
|
25
|
+
- [Authentication](authentication.md)
|
|
26
|
+
- [Products](products.md)
|
|
27
|
+
- [Receipts](receipts.md)
|
|
28
|
+
- [Shopping Lists](lists.md)
|
|
29
|
+
- [Development](development.md)
|