amazon-orders-de 1.0.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.
Files changed (47) hide show
  1. amazon_orders_de-1.0.0/.github/workflows/integration.yml +45 -0
  2. amazon_orders_de-1.0.0/.github/workflows/lint.yml +91 -0
  3. amazon_orders_de-1.0.0/.github/workflows/mr-test.yml +74 -0
  4. amazon_orders_de-1.0.0/.github/workflows/mr-title.yml +21 -0
  5. amazon_orders_de-1.0.0/.github/workflows/publish.yml +32 -0
  6. amazon_orders_de-1.0.0/.github/workflows/release-automerge-weekly.yml +31 -0
  7. amazon_orders_de-1.0.0/.github/workflows/release-please.yml +21 -0
  8. amazon_orders_de-1.0.0/.gitignore +21 -0
  9. amazon_orders_de-1.0.0/.markdownlint-cli2.yaml +14 -0
  10. amazon_orders_de-1.0.0/.release-please-manifest.json +3 -0
  11. amazon_orders_de-1.0.0/.yamllint.yml +18 -0
  12. amazon_orders_de-1.0.0/CHANGELOG.md +13 -0
  13. amazon_orders_de-1.0.0/LICENSE +21 -0
  14. amazon_orders_de-1.0.0/PKG-INFO +78 -0
  15. amazon_orders_de-1.0.0/README.md +64 -0
  16. amazon_orders_de-1.0.0/amazon_orders_de/__init__.py +11 -0
  17. amazon_orders_de-1.0.0/amazon_orders_de/constants.py +21 -0
  18. amazon_orders_de-1.0.0/amazon_orders_de/order.py +46 -0
  19. amazon_orders_de-1.0.0/amazon_orders_de/selectors.py +30 -0
  20. amazon_orders_de-1.0.0/amazon_orders_de/transactions.py +157 -0
  21. amazon_orders_de-1.0.0/lychee.toml +18 -0
  22. amazon_orders_de-1.0.0/mise.toml +31 -0
  23. amazon_orders_de-1.0.0/pyproject.toml +63 -0
  24. amazon_orders_de-1.0.0/release-please-config.json +21 -0
  25. amazon_orders_de-1.0.0/renovate.json +23 -0
  26. amazon_orders_de-1.0.0/scripts/anonymize-test-resources.py +719 -0
  27. amazon_orders_de-1.0.0/tests/__init__.py +0 -0
  28. amazon_orders_de-1.0.0/tests/integration/__init__.py +0 -0
  29. amazon_orders_de-1.0.0/tests/integration/test_integration.py +61 -0
  30. amazon_orders_de-1.0.0/tests/resources/orders/order-details-302-1023958-8934207.html +734 -0
  31. amazon_orders_de-1.0.0/tests/resources/orders/order-details-302-6964083-0174099.html +638 -0
  32. amazon_orders_de-1.0.0/tests/resources/orders/order-details-305-1022088-4781474.html +650 -0
  33. amazon_orders_de-1.0.0/tests/resources/orders/order-details-305-1086546-4564812.html +515 -0
  34. amazon_orders_de-1.0.0/tests/resources/orders/order-details-305-1455896-8790496.html +573 -0
  35. amazon_orders_de-1.0.0/tests/resources/orders/order-details-305-9788051-7308445.html +602 -0
  36. amazon_orders_de-1.0.0/tests/resources/orders/order-details-306-9272323-1520652.html +638 -0
  37. amazon_orders_de-1.0.0/tests/resources/orders/order-details-D01-7485423-1791348.html +527 -0
  38. amazon_orders_de-1.0.0/tests/resources/orders/order-history-2024-0.html +899 -0
  39. amazon_orders_de-1.0.0/tests/resources/orders/order-history-2026-0.html +1135 -0
  40. amazon_orders_de-1.0.0/tests/resources/orders/order-history-2026-120.html +408 -0
  41. amazon_orders_de-1.0.0/tests/resources/orders/order-history-2026-140.html +419 -0
  42. amazon_orders_de-1.0.0/tests/resources/orders/order-history-2026-60.html +385 -0
  43. amazon_orders_de-1.0.0/tests/resources/transactions/transactions-api-page-2.json +1 -0
  44. amazon_orders_de-1.0.0/tests/resources/transactions/transactions.html +268 -0
  45. amazon_orders_de-1.0.0/tests/unit/__init__.py +0 -0
  46. amazon_orders_de-1.0.0/tests/unit/test_amazon_de.py +415 -0
  47. amazon_orders_de-1.0.0/uv.lock +993 -0
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: Integration Test
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ schedule:
7
+ # Weekly, Monday morning: a login every day makes Amazon more likely to challenge it.
8
+ - cron: "0 7 * * 1"
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: amazon-orders-de-integration-singleton
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 30
20
+ env:
21
+ PYTHONUNBUFFERED: 1
22
+ # The logs of a public repo are public, and debug output logs every URL, with the account's Order numbers.
23
+ DEBUG: "False"
24
+ AMAZON_USERNAME: ${{ secrets.AMAZON_USERNAME }}
25
+ AMAZON_PASSWORD: ${{ secrets.AMAZON_PASSWORD }}
26
+ AMAZON_OTP_SECRET_KEY: ${{ secrets.AMAZON_OTP_SECRET_KEY }}
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ with:
30
+ persist-credentials: false
31
+
32
+ - name: Verify credentials are set
33
+ run: |
34
+ if [ -z "$AMAZON_USERNAME" ] || [ -z "$AMAZON_PASSWORD" ]; then
35
+ echo "::error::Set the AMAZON_USERNAME and AMAZON_PASSWORD repo secrets."
36
+ exit 1
37
+ fi
38
+
39
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
40
+
41
+ - name: Install dependencies
42
+ run: mise run install
43
+
44
+ - name: Integration test
45
+ run: mise run integration-test
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: Lint
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - main
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ # Python is linted in mr-test.yml. Each linter here is its own job so a failure names the file type. The linters'
16
+ # versions are in mise.toml, where Renovate updates them.
17
+ jobs:
18
+ markdown:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
+ with:
23
+ persist-credentials: false
24
+
25
+ # Files and rules come from .markdownlint-cli2.yaml, whose globs add to the action's default of the root files.
26
+ - uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
27
+
28
+ markdown-links:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ with:
33
+ persist-credentials: false
34
+
35
+ - name: Restore lychee cache
36
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
37
+ with:
38
+ path: .lycheecache
39
+ key: lychee-${{ github.sha }}
40
+ restore-keys: lychee-
41
+
42
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
43
+ with:
44
+ install_args: lychee
45
+
46
+ - name: lychee
47
+ env:
48
+ # Authenticated requests to GitHub links are not rate limited as quickly.
49
+ GITHUB_TOKEN: ${{ github.token }}
50
+ run: lychee --config lychee.toml --no-progress README.md
51
+
52
+ yaml:
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
56
+ with:
57
+ persist-credentials: false
58
+
59
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
60
+ with:
61
+ install_args: python uv pypi:yamllint
62
+
63
+ # Files and rules come from .yamllint.yml.
64
+ - name: yamllint
65
+ run: yamllint --strict --format github .
66
+
67
+ actions:
68
+ runs-on: ubuntu-latest
69
+ steps:
70
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
71
+ with:
72
+ persist-credentials: false
73
+
74
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
75
+ with:
76
+ install_args: actionlint shellcheck
77
+
78
+ # Also runs shellcheck over every `run:` block.
79
+ - name: actionlint
80
+ run: actionlint
81
+
82
+ json:
83
+ runs-on: ubuntu-latest
84
+ steps:
85
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
86
+ with:
87
+ persist-credentials: false
88
+
89
+ # Syntax only: release-please owns the formatting of the files it writes.
90
+ - name: jq
91
+ run: git ls-files -z '*.json' | xargs -0 -n1 jq empty
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: MR Test
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ push:
7
+ branches:
8
+ - main
9
+ pull_request:
10
+ branches:
11
+ - main
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ test:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 15
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ python:
28
+ # The oldest supported version (requires-python).
29
+ - "3.10"
30
+ # The one of mise.toml. Renovate updates both in the same PR.
31
+ # renovate: python
32
+ - "3.15.0"
33
+ env:
34
+ UV_PYTHON: ${{ matrix.python }}
35
+ steps:
36
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37
+ with:
38
+ persist-credentials: false
39
+
40
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
41
+
42
+ - name: Install dependencies
43
+ run: mise run install
44
+
45
+ - name: Lint
46
+ run: mise run lint
47
+
48
+ - name: Test
49
+ run: mise run test
50
+
51
+ build:
52
+ runs-on: ubuntu-latest
53
+ timeout-minutes: 10
54
+ steps:
55
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
56
+ with:
57
+ persist-credentials: false
58
+
59
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
60
+
61
+ - name: Build
62
+ run: uv build
63
+
64
+ # One status check for branch protection, whatever jobs are added later.
65
+ required:
66
+ runs-on: ubuntu-latest
67
+ needs: [test, build]
68
+ if: always()
69
+ steps:
70
+ - name: Check result
71
+ run: |
72
+ if [[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
73
+ exit 1
74
+ fi
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: MR Title
3
+
4
+ on:
5
+ pull_request:
6
+ types:
7
+ - opened
8
+ - edited
9
+ - synchronize
10
+ - reopened
11
+
12
+ permissions:
13
+ pull-requests: read
14
+
15
+ jobs:
16
+ lint-pr-title:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
20
+ env:
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: Publish to PyPI
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ release:
7
+ types: [published]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 10
16
+ environment:
17
+ name: pypi
18
+ url: https://pypi.org/p/amazon-orders-de
19
+ permissions:
20
+ id-token: write
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
27
+
28
+ - name: Build
29
+ run: uv build
30
+
31
+ # Trusted publishing: PyPI trusts this workflow and the pypi environment, no token needed.
32
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: Release Automerge
3
+
4
+ on:
5
+ schedule:
6
+ - cron: 0 18 * * 0
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+ pull-requests: write
12
+
13
+ jobs:
14
+ merge-latest-release:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ # OVERRIDE_TOKEN is required: a merge made with GITHUB_TOKEN would not
18
+ # trigger the release-please workflow that creates the release.
19
+ - name: Merge release PR
20
+ run: |
21
+ pr=$(gh pr list --repo "${GITHUB_REPOSITORY}" \
22
+ --head release-please--branches--main --state open \
23
+ --json number --jq '.[0].number // empty')
24
+ if [ -z "${pr}" ]; then
25
+ echo "No open release PR."
26
+ exit 0
27
+ fi
28
+ gh pr checks "${pr}" --repo "${GITHUB_REPOSITORY}"
29
+ gh pr merge "${pr}" --squash --repo "${GITHUB_REPOSITORY}"
30
+ env:
31
+ GH_TOKEN: ${{ secrets.OVERRIDE_TOKEN }}
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: release-please
3
+
4
+ on:
5
+ workflow_dispatch:
6
+ push:
7
+ branches:
8
+ - main
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ release-please:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ # OVERRIDE_TOKEN is required: a release created with GITHUB_TOKEN would not trigger publish.yml.
19
+ - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
20
+ with:
21
+ token: ${{ secrets.OVERRIDE_TOKEN }}
@@ -0,0 +1,21 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ .lycheecache
12
+ .coverage
13
+ htmlcov/
14
+
15
+ # Debug output of amazon-orders (real, unanonymized pages)
16
+ output/
17
+ # Raw pages captured from a real account, before they are anonymized
18
+ tests/resources-raw/
19
+ tests/.integration-output/
20
+
21
+ .claude/worktrees/
@@ -0,0 +1,14 @@
1
+ ---
2
+ config:
3
+ # Prose is written one paragraph per line, and tables and command lines can't be wrapped.
4
+ MD013: false
5
+
6
+ globs:
7
+ - "**/*.md"
8
+
9
+ ignores:
10
+ # Generated by release-please.
11
+ - CHANGELOG.md
12
+ - .venv/**
13
+ - venv/**
14
+ - .claude/**
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.0.0"
3
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ extends: default
3
+
4
+ ignore: |
5
+ .venv/
6
+ venv/
7
+ .claude/
8
+
9
+ rules:
10
+ # `on:` is a workflow key, not a boolean.
11
+ truthy:
12
+ check-keys: false
13
+ # Workflow comments and step commands explain themselves on one line.
14
+ line-length:
15
+ max: 160
16
+ # SHA-pinned actions carry their version as `@sha # vX.Y.Z`.
17
+ comments:
18
+ min-spaces-from-content: 1
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-10-13)
4
+
5
+
6
+ ### Features
7
+
8
+ * amazon.de language package that parses its pages in English ([#1](https://github.com/udondan/amazon-orders-de/issues/1)) ([197a205](https://github.com/udondan/amazon-orders-de/commit/197a205192dffa09924b12dedb80985fbd6e1f99))
9
+
10
+
11
+ ### Miscellaneous Chores
12
+
13
+ * release 1.0.0 ([#11](https://github.com/udondan/amazon-orders-de/issues/11)) ([809d9a1](https://github.com/udondan/amazon-orders-de/commit/809d9a153538a7e5e06326d4cc7842716379d0f5))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Schroeder
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.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.5
2
+ Name: amazon-orders-de
3
+ Version: 1.0.0
4
+ Summary: An amazon-orders language package for amazon.de, which parses its pages in English
5
+ Project-URL: Source, https://github.com/udondan/amazon-orders-de
6
+ Author: Daniel Schroeder
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: amazon-orders<5,>=4.8.1
13
+ Description-Content-Type: text/markdown
14
+
15
+ # amazon-orders-de
16
+
17
+ A [language package](https://amazon-orders.readthedocs.io/language-packages.html) for
18
+ [amazon-orders](https://github.com/alexdlaird/amazon-orders) that adds support for amazon.de.
19
+
20
+ Rather than translating `amazon-orders` into German, it asks amazon.de for its pages in English and handles what's
21
+ different there:
22
+
23
+ - British English labels ("Postage & Packing", "Estimated VAT")
24
+ - Orders whose service was cancelled
25
+ - Recipient addresses rendered as a plain list
26
+ - The payment method of an Order paid with a Gift Card and a card
27
+ - The Transactions page, which embeds its first page as JSON and loads further pages from an API
28
+
29
+ ## Installation
30
+
31
+ ```sh
32
+ pip install amazon-orders-de
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ Set `language_package` and `domain` in the `amazon-orders` config (`~/.config/amazonorders/config.yml`):
38
+
39
+ ```yaml
40
+ language_package: amazon_orders_de
41
+ domain: amazon.de
42
+ ```
43
+
44
+ or pass them in Python:
45
+
46
+ ```python
47
+ from amazonorders.conf import AmazonOrdersConfig
48
+ from amazonorders.orders import AmazonOrders
49
+ from amazonorders.session import AmazonSession
50
+
51
+ config = AmazonOrdersConfig(data={"language_package": "amazon_orders_de", "domain": "amazon.de"})
52
+ amazon_session = AmazonSession(config=config)
53
+ amazon_session.login()
54
+
55
+ orders = AmazonOrders(amazon_session).get_order_history(year=2026)
56
+ ```
57
+
58
+ Everything else works as described in the [amazon-orders docs](https://amazon-orders.readthedocs.io).
59
+
60
+ ## Development
61
+
62
+ With [mise](https://mise.jdx.dev/), which installs Python and uv:
63
+
64
+ ```sh
65
+ mise run install # uv sync
66
+ mise run lint # ruff and mypy; `mise run lint-fix` fixes what it can
67
+ mise run test
68
+ ```
69
+
70
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/), from which release-please creates the
71
+ releases and the changelog.
72
+
73
+ The unit tests parse real amazon.de pages in `tests/resources`. Before a page is added there, it's anonymized with
74
+ `scripts/anonymize-test-resources.py`, which removes personal and product details. The raw pages and the rules file
75
+ with the values to replace must never be committed.
76
+
77
+ The integration tests in `tests/integration` log in to amazon.de with `AMAZON_USERNAME`, `AMAZON_PASSWORD`, and
78
+ `AMAZON_OTP_SECRET_KEY`, and run weekly in CI.
@@ -0,0 +1,64 @@
1
+ # amazon-orders-de
2
+
3
+ A [language package](https://amazon-orders.readthedocs.io/language-packages.html) for
4
+ [amazon-orders](https://github.com/alexdlaird/amazon-orders) that adds support for amazon.de.
5
+
6
+ Rather than translating `amazon-orders` into German, it asks amazon.de for its pages in English and handles what's
7
+ different there:
8
+
9
+ - British English labels ("Postage & Packing", "Estimated VAT")
10
+ - Orders whose service was cancelled
11
+ - Recipient addresses rendered as a plain list
12
+ - The payment method of an Order paid with a Gift Card and a card
13
+ - The Transactions page, which embeds its first page as JSON and loads further pages from an API
14
+
15
+ ## Installation
16
+
17
+ ```sh
18
+ pip install amazon-orders-de
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ Set `language_package` and `domain` in the `amazon-orders` config (`~/.config/amazonorders/config.yml`):
24
+
25
+ ```yaml
26
+ language_package: amazon_orders_de
27
+ domain: amazon.de
28
+ ```
29
+
30
+ or pass them in Python:
31
+
32
+ ```python
33
+ from amazonorders.conf import AmazonOrdersConfig
34
+ from amazonorders.orders import AmazonOrders
35
+ from amazonorders.session import AmazonSession
36
+
37
+ config = AmazonOrdersConfig(data={"language_package": "amazon_orders_de", "domain": "amazon.de"})
38
+ amazon_session = AmazonSession(config=config)
39
+ amazon_session.login()
40
+
41
+ orders = AmazonOrders(amazon_session).get_order_history(year=2026)
42
+ ```
43
+
44
+ Everything else works as described in the [amazon-orders docs](https://amazon-orders.readthedocs.io).
45
+
46
+ ## Development
47
+
48
+ With [mise](https://mise.jdx.dev/), which installs Python and uv:
49
+
50
+ ```sh
51
+ mise run install # uv sync
52
+ mise run lint # ruff and mypy; `mise run lint-fix` fixes what it can
53
+ mise run test
54
+ ```
55
+
56
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/), from which release-please creates the
57
+ releases and the changelog.
58
+
59
+ The unit tests parse real amazon.de pages in `tests/resources`. Before a page is added there, it's anonymized with
60
+ `scripts/anonymize-test-resources.py`, which removes personal and product details. The raw pages and the rules file
61
+ with the values to replace must never be committed.
62
+
63
+ The integration tests in `tests/integration` log in to amazon.de with `AMAZON_USERNAME`, `AMAZON_PASSWORD`, and
64
+ `AMAZON_OTP_SECRET_KEY`, and run weekly in CI.
@@ -0,0 +1,11 @@
1
+ """
2
+ An ``amazon-orders`` language package for amazon.de, which requests its pages in English. Enable it with
3
+ ``language_package: amazon_orders_de`` and ``domain: amazon.de`` in the config.
4
+ """
5
+
6
+ from amazon_orders_de.constants import Constants
7
+ from amazon_orders_de.order import Order
8
+ from amazon_orders_de.selectors import Selectors
9
+ from amazon_orders_de.transactions import TransactionsPage
10
+
11
+ __all__ = ["Constants", "Order", "Selectors", "TransactionsPage"]
@@ -0,0 +1,21 @@
1
+ from amazonorders import constants
2
+
3
+
4
+ class Constants(constants.Constants):
5
+ """
6
+ Constants for amazon.de, requested in English.
7
+ """
8
+
9
+ # Ask for the English version of the site, so its pages are parsed with the English words of ``amazon-orders``
10
+ BASE_HEADERS = {**constants.Constants.BASE_HEADERS, "Accept-Language": "en-GB,en;q=0.9"}
11
+
12
+ # amazon.de picks its language from this cookie rather than from ``Accept-Language``, and sets it again from the
13
+ # account's language on every response
14
+ REQUEST_COOKIES = {"lc-acbde": "en_GB"}
15
+
16
+ #: The internal API that loads further pages of Transactions.
17
+ TRANSACTION_HISTORY_API_ROUTE = "/payments-portal/data/iris/live/v1/data/manage/get-transactions"
18
+
19
+ @property
20
+ def TRANSACTION_HISTORY_API_URL(self) -> str:
21
+ return f"{self.BASE_URL}{self.TRANSACTION_HISTORY_API_ROUTE}"
@@ -0,0 +1,46 @@
1
+ import re
2
+
3
+ from amazonorders import util
4
+ from amazonorders.entity import order, recipient
5
+
6
+
7
+ class Recipient(recipient.Recipient):
8
+ """
9
+ A Recipient whose address is rendered as an unclassed list, one or more lines per list item.
10
+ """
11
+
12
+ def _parse_address(self) -> str | None:
13
+ return self._parse_address_lines() or super()._parse_address()
14
+
15
+ def _parse_address_lines(self) -> str | None:
16
+ lines: list[str] = []
17
+ for tag in util.select(self.parsed, self.config.selectors.FIELD_RECIPIENT_ADDRESS_LINES_SELECTOR):
18
+ # A list item may hold several lines separated by <br> or, when Amazon renders the address inline,
19
+ # by commas
20
+ lines.extend(line.strip() for line in re.split(r"[\n,]", tag.get_text("\n")) if line.strip())
21
+
22
+ return "\n".join(lines) or None
23
+
24
+
25
+ class Order(order.Order):
26
+ """
27
+ An Order on amazon.de.
28
+ """
29
+
30
+ def _parse_payment_method(self) -> str | None:
31
+ # When a Gift Card was used alongside a card, prefer the card (the instrument with a number), like its last
32
+ # digits are
33
+ selectors = self.config.selectors
34
+ for instrument in util.select(self.parsed, selectors.FIELD_ORDER_PAYMENT_INSTRUMENT_SELECTOR):
35
+ name_tag = util.select_one(instrument, selectors.FIELD_ORDER_PAYMENT_INSTRUMENT_NAME_SELECTOR)
36
+ if name_tag and util.select_one(instrument, selectors.FIELD_ORDER_PAYMENT_INSTRUMENT_NUMBER_SELECTOR):
37
+ return name_tag.text.strip() or None
38
+
39
+ return super()._parse_payment_method()
40
+
41
+ def _parse_recipient(self) -> recipient.Recipient | None:
42
+ value = super()._parse_recipient()
43
+ if value is None:
44
+ return None
45
+
46
+ return Recipient(value.parsed, self.config)
@@ -0,0 +1,30 @@
1
+ from amazonorders import selectors
2
+ from amazonorders.selectors import Selector
3
+
4
+
5
+ class Selectors(selectors.Selectors):
6
+ """
7
+ Selectors for amazon.de, requested in English, which uses British English labels.
8
+ """
9
+
10
+ # Rendered as "Postage & Packing"
11
+ FIELD_ORDER_SHIPPING_TOTAL_LABELS = [*selectors.Selectors.FIELD_ORDER_SHIPPING_TOTAL_LABELS, "postage"]
12
+ # Rendered as "Estimated VAT"
13
+ FIELD_ORDER_ESTIMATED_TAX_LABELS = [*selectors.Selectors.FIELD_ORDER_ESTIMATED_TAX_LABELS, "estimated vat"]
14
+
15
+ ORDER_SKIP_TOTALS = [
16
+ *selectors.Selectors.ORDER_SKIP_TOTALS,
17
+ # Identifies an Order of a cancelled service (e.g. a cancelled installation) on the history page
18
+ Selector("div.yohtmlc-shipment-status-primaryText", "Service cancelled"),
19
+ ]
20
+
21
+ #: The payment instruments an Order was paid with, and each one's name and masked number.
22
+ FIELD_ORDER_PAYMENT_INSTRUMENT_SELECTOR = "[data-testid='payment-instrument']"
23
+ FIELD_ORDER_PAYMENT_INSTRUMENT_NAME_SELECTOR = "[data-testid='payment-instrument-name']"
24
+ FIELD_ORDER_PAYMENT_INSTRUMENT_NUMBER_SELECTOR = "[data-testid='payment-instrument-number']"
25
+
26
+ #: The address lines that follow the Recipient's name in an unclassed list.
27
+ FIELD_RECIPIENT_ADDRESS_LINES_SELECTOR = "ul > li:not(:first-child)"
28
+
29
+ #: The Transactions, embedded as JSON in the Next.js page.
30
+ TRANSACTION_HISTORY_NEXT_DATA_SELECTOR = "script#__NEXT_DATA__"