shipzil 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.
Files changed (99) hide show
  1. shipzil-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +84 -0
  2. shipzil-0.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. shipzil-0.1.0/.github/ISSUE_TEMPLATE/provider_support.yml +50 -0
  4. shipzil-0.1.0/.github/dependabot.yml +37 -0
  5. shipzil-0.1.0/.github/pull_request_template.md +29 -0
  6. shipzil-0.1.0/.github/workflows/ci.yml +48 -0
  7. shipzil-0.1.0/.github/workflows/docs.yml +67 -0
  8. shipzil-0.1.0/.github/workflows/release.yml +78 -0
  9. shipzil-0.1.0/.gitignore +22 -0
  10. shipzil-0.1.0/.python-version +1 -0
  11. shipzil-0.1.0/AGENTS.md +85 -0
  12. shipzil-0.1.0/CHANGELOG.md +77 -0
  13. shipzil-0.1.0/CONTRIBUTING.md +115 -0
  14. shipzil-0.1.0/LICENSE +21 -0
  15. shipzil-0.1.0/Makefile +48 -0
  16. shipzil-0.1.0/PKG-INFO +260 -0
  17. shipzil-0.1.0/PLAN.md +49 -0
  18. shipzil-0.1.0/README.md +236 -0
  19. shipzil-0.1.0/SECURITY.md +44 -0
  20. shipzil-0.1.0/UPGRADE_GUIDE.md +7 -0
  21. shipzil-0.1.0/assets/shipzil-mark.svg +19 -0
  22. shipzil-0.1.0/docs-site/.gitignore +31 -0
  23. shipzil-0.1.0/docs-site/README.md +33 -0
  24. shipzil-0.1.0/docs-site/app/(home)/layout.tsx +18 -0
  25. shipzil-0.1.0/docs-site/app/(home)/page.tsx +205 -0
  26. shipzil-0.1.0/docs-site/app/api/search/route.ts +9 -0
  27. shipzil-0.1.0/docs-site/app/docs/[[...slug]]/page.tsx +63 -0
  28. shipzil-0.1.0/docs-site/app/docs/layout.tsx +11 -0
  29. shipzil-0.1.0/docs-site/app/global.css +37 -0
  30. shipzil-0.1.0/docs-site/app/icon.svg +21 -0
  31. shipzil-0.1.0/docs-site/app/layout.tsx +55 -0
  32. shipzil-0.1.0/docs-site/app/llms-full.txt/route.ts +10 -0
  33. shipzil-0.1.0/docs-site/app/llms.mdx/docs/[[...slug]]/route.ts +23 -0
  34. shipzil-0.1.0/docs-site/app/llms.txt/route.ts +12 -0
  35. shipzil-0.1.0/docs-site/app/og/docs/[...slug]/route.tsx +110 -0
  36. shipzil-0.1.0/docs-site/components/logo.tsx +39 -0
  37. shipzil-0.1.0/docs-site/components/mdx.tsx +17 -0
  38. shipzil-0.1.0/docs-site/components/provider.tsx +8 -0
  39. shipzil-0.1.0/docs-site/components/search.tsx +38 -0
  40. shipzil-0.1.0/docs-site/content/docs/concepts.md +162 -0
  41. shipzil-0.1.0/docs-site/content/docs/errors.md +164 -0
  42. shipzil-0.1.0/docs-site/content/docs/index.md +99 -0
  43. shipzil-0.1.0/docs-site/content/docs/international.md +134 -0
  44. shipzil-0.1.0/docs-site/content/docs/meta.json +16 -0
  45. shipzil-0.1.0/docs-site/content/docs/provider-sources.md +55 -0
  46. shipzil-0.1.0/docs-site/content/docs/providers.md +121 -0
  47. shipzil-0.1.0/docs-site/content/docs/quickstart.md +144 -0
  48. shipzil-0.1.0/docs-site/content/docs/reference.md +296 -0
  49. shipzil-0.1.0/docs-site/content/docs/research.md +51 -0
  50. shipzil-0.1.0/docs-site/content/docs/roadmap.md +56 -0
  51. shipzil-0.1.0/docs-site/lib/cn.ts +1 -0
  52. shipzil-0.1.0/docs-site/lib/layout.shared.tsx +17 -0
  53. shipzil-0.1.0/docs-site/lib/shared.ts +12 -0
  54. shipzil-0.1.0/docs-site/lib/source.ts +53 -0
  55. shipzil-0.1.0/docs-site/next.config.mjs +16 -0
  56. shipzil-0.1.0/docs-site/package-lock.json +7526 -0
  57. shipzil-0.1.0/docs-site/package.json +38 -0
  58. shipzil-0.1.0/docs-site/postcss.config.mjs +7 -0
  59. shipzil-0.1.0/docs-site/scripts/generate-python-api.mjs +201 -0
  60. shipzil-0.1.0/docs-site/tsconfig.json +34 -0
  61. shipzil-0.1.0/examples/README.md +16 -0
  62. shipzil-0.1.0/examples/gateway.py +69 -0
  63. shipzil-0.1.0/pyproject.toml +68 -0
  64. shipzil-0.1.0/shipzil/__init__.py +82 -0
  65. shipzil-0.1.0/shipzil/_client.py +189 -0
  66. shipzil-0.1.0/shipzil/errors.py +92 -0
  67. shipzil-0.1.0/shipzil/gateway.py +566 -0
  68. shipzil-0.1.0/shipzil/http.py +264 -0
  69. shipzil-0.1.0/shipzil/models.py +656 -0
  70. shipzil-0.1.0/shipzil/multiparcel.py +170 -0
  71. shipzil-0.1.0/shipzil/normalize.py +71 -0
  72. shipzil-0.1.0/shipzil/providers/__init__.py +41 -0
  73. shipzil-0.1.0/shipzil/providers/base.py +398 -0
  74. shipzil-0.1.0/shipzil/providers/easyship.py +572 -0
  75. shipzil-0.1.0/shipzil/providers/shippo.py +457 -0
  76. shipzil-0.1.0/shipzil/providers/shipstation_v1.py +506 -0
  77. shipzil-0.1.0/shipzil/providers/shipstation_v2.py +448 -0
  78. shipzil-0.1.0/shipzil/py.typed +0 -0
  79. shipzil-0.1.0/shipzil/services.py +357 -0
  80. shipzil-0.1.0/shipzil/units.py +108 -0
  81. shipzil-0.1.0/tests/fixtures/README.md +35 -0
  82. shipzil-0.1.0/tests/fixtures/es_label.json +238 -0
  83. shipzil-0.1.0/tests/fixtures/es_rates_multi.json +28 -0
  84. shipzil-0.1.0/tests/fixtures/es_rates_single.json +402 -0
  85. shipzil-0.1.0/tests/fixtures/shippo_multiparcel.json +181 -0
  86. shipzil-0.1.0/tests/fixtures/shippo_single.json +482 -0
  87. shipzil-0.1.0/tests/fixtures/ss1_carriers.json +22 -0
  88. shipzil-0.1.0/tests/fixtures/ss1_rates_single.json +116 -0
  89. shipzil-0.1.0/tests/fixtures/ss1_testlabel.json +33 -0
  90. shipzil-0.1.0/tests/fixtures/ss2_carriers.json +792 -0
  91. shipzil-0.1.0/tests/fixtures/ss2_rates_multi.json +642 -0
  92. shipzil-0.1.0/tests/fixtures/ss2_rates_single.json +1720 -0
  93. shipzil-0.1.0/tests/test_gateway.py +570 -0
  94. shipzil-0.1.0/tests/test_http.py +233 -0
  95. shipzil-0.1.0/tests/test_logistics.py +1072 -0
  96. shipzil-0.1.0/tests/test_real_payloads.py +542 -0
  97. shipzil-0.1.0/tests/test_shipzil.py +594 -0
  98. shipzil-0.1.0/tests/transport.py +68 -0
  99. shipzil-0.1.0/uv.lock +469 -0
@@ -0,0 +1,84 @@
1
+ name: Bug report
2
+ description: Something behaves differently from the documented contract.
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Please do not include live credentials, real customer addresses or real
9
+ label data. Sanitize provider payloads the way `tests/fixtures/README.md`
10
+ describes.
11
+
12
+ For a security issue, use a
13
+ [private advisory](https://github.com/sameerkumar18/shipzil/security/advisories/new)
14
+ instead.
15
+
16
+ - type: input
17
+ id: version
18
+ attributes:
19
+ label: shipzil version or commit
20
+ placeholder: "0.1.0, or the commit SHA from your lockfile"
21
+ validations:
22
+ required: true
23
+
24
+ - type: dropdown
25
+ id: provider
26
+ attributes:
27
+ label: Provider
28
+ options:
29
+ - shippo
30
+ - easyship
31
+ - shipstation_v1
32
+ - shipstation_v2
33
+ - more than one
34
+ - not provider specific
35
+ validations:
36
+ required: true
37
+
38
+ - type: dropdown
39
+ id: operation
40
+ attributes:
41
+ label: Operation
42
+ options:
43
+ - rating
44
+ - purchase
45
+ - void or refund
46
+ - filtering or service keys
47
+ - customs or international
48
+ - other
49
+ validations:
50
+ required: true
51
+
52
+ - type: textarea
53
+ id: expected
54
+ attributes:
55
+ label: What you expected, and where that is documented
56
+ description: Link the docs page or quote the contract, so we can tell a bug from a missing feature.
57
+ validations:
58
+ required: true
59
+
60
+ - type: textarea
61
+ id: actual
62
+ attributes:
63
+ label: What happened
64
+ description: Include `quote.explain()` output where relevant, and the exception type and message.
65
+ render: text
66
+ validations:
67
+ required: true
68
+
69
+ - type: textarea
70
+ id: repro
71
+ attributes:
72
+ label: Reproduction
73
+ description: The smallest snippet that shows it. Replace credentials with placeholders.
74
+ render: python
75
+ validations:
76
+ required: true
77
+
78
+ - type: input
79
+ id: python
80
+ attributes:
81
+ label: Python version
82
+ placeholder: "3.12.4"
83
+ validations:
84
+ required: true
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Documentation
4
+ url: https://sameerkumar18.github.io/shipzil/
5
+ about: Concepts, provider support, errors and the API reference.
6
+ - name: Security issue
7
+ url: https://github.com/sameerkumar18/shipzil/security/advisories/new
8
+ about: Report credential handling, duplicate purchase or declaration issues privately.
@@ -0,0 +1,50 @@
1
+ name: Provider support
2
+ description: Add a provider, or report a gap in an existing adapter.
3
+ labels: [provider]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Adapter changes need evidence from the provider's current schema, not its
9
+ prose documentation. See CONTRIBUTING.md.
10
+
11
+ - type: input
12
+ id: provider
13
+ attributes:
14
+ label: Provider
15
+ placeholder: "Sendcloud, or shipstation_v2"
16
+ validations:
17
+ required: true
18
+
19
+ - type: dropdown
20
+ id: kind
21
+ attributes:
22
+ label: Request
23
+ options:
24
+ - a new provider adapter
25
+ - a field or operation an existing adapter does not send
26
+ - a provider behaviour shipzil reports incorrectly
27
+ validations:
28
+ required: true
29
+
30
+ - type: textarea
31
+ id: evidence
32
+ attributes:
33
+ label: Schema evidence
34
+ description: |
35
+ Link the provider's schema or model page for the fields involved, and quote
36
+ the relevant part. A prose example is not enough; field nesting and
37
+ required values have to come from the schema.
38
+ validations:
39
+ required: true
40
+
41
+ - type: textarea
42
+ id: semantics
43
+ attributes:
44
+ label: Semantics that differ from the current adapters
45
+ description: |
46
+ Especially: is a customs value per unit or a line total, is multi-parcel
47
+ rating native, how is duty liability expressed, and is there a no-charge
48
+ test path for purchases.
49
+ validations:
50
+ required: false
@@ -0,0 +1,37 @@
1
+ version: 2
2
+
3
+ updates:
4
+ # Python: runtime has no dependencies, so this tracks the dev toolchain only.
5
+ - package-ecosystem: uv
6
+ directory: /
7
+ schedule:
8
+ interval: monthly
9
+ open-pull-requests-limit: 3
10
+ commit-message:
11
+ prefix: "deps"
12
+
13
+ - package-ecosystem: npm
14
+ directory: /docs-site
15
+ schedule:
16
+ interval: monthly
17
+ open-pull-requests-limit: 3
18
+ commit-message:
19
+ prefix: "deps(docs)"
20
+ groups:
21
+ fumadocs:
22
+ patterns:
23
+ - "fumadocs*"
24
+ next:
25
+ patterns:
26
+ - "next"
27
+ - "react"
28
+ - "react-dom"
29
+ - "@types/react*"
30
+
31
+ # setup-uv has no moving major tag, so a pinned version needs updating here.
32
+ - package-ecosystem: github-actions
33
+ directory: /
34
+ schedule:
35
+ interval: monthly
36
+ commit-message:
37
+ prefix: "ci"
@@ -0,0 +1,29 @@
1
+ ## What changes
2
+
3
+ <!-- The behaviour a caller can observe, not the diff. -->
4
+
5
+ ## Evidence
6
+
7
+ <!-- For a provider change: link the provider's current schema or model page for
8
+ every field involved. A prose example is not sufficient. -->
9
+
10
+ - [ ] Request fields verified against the provider's current schema
11
+ - [ ] Payload test proves the value reaches the outgoing request
12
+ - [ ] Parser test uses a sanitized provider response, or the constructed case is
13
+ marked synthetic
14
+ - [ ] Regression test checked by restoring the bug and watching it fail
15
+
16
+ ## Safety
17
+
18
+ - [ ] `retries=0` on anything that spends money
19
+ - [ ] No fabricated dimensions, contents, customs values or company names
20
+ - [ ] `Quote.excluded` explains anything shipzil removed
21
+ - [ ] Docs updated where behaviour changed
22
+
23
+ ## Verification
24
+
25
+ ```
26
+ make check
27
+ make check-compat
28
+ make test-live # state which live tests ran, and against which provider
29
+ ```
@@ -0,0 +1,48 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "shipzil/**"
8
+ - "tests/**"
9
+ - "pyproject.toml"
10
+ - "uv.lock"
11
+ - ".github/workflows/ci.yml"
12
+ pull_request:
13
+ paths:
14
+ - "shipzil/**"
15
+ - "tests/**"
16
+ - "pyproject.toml"
17
+ - "uv.lock"
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ quality:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v7
27
+ # setup-uv publishes no moving major tag, so this is pinned exactly.
28
+ - uses: astral-sh/setup-uv@v10.1.0
29
+ with:
30
+ enable-cache: true
31
+ - run: uv sync --python 3.14
32
+ - run: uv run ruff check shipzil tests
33
+ - run: uv run mypy shipzil
34
+ - run: uv run pytest -m "not live"
35
+
36
+ compatibility:
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
41
+ runs-on: ubuntu-latest
42
+ steps:
43
+ - uses: actions/checkout@v7
44
+ # setup-uv publishes no moving major tag, so this is pinned exactly.
45
+ - uses: astral-sh/setup-uv@v10.1.0
46
+ with:
47
+ enable-cache: true
48
+ - run: uv run --python ${{ matrix.python-version }} --isolated --with pytest pytest -m "not live"
@@ -0,0 +1,67 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs-site/**"
8
+ - "shipzil/**"
9
+ - "AGENTS.md"
10
+ - "pyproject.toml"
11
+ - ".github/workflows/docs.yml"
12
+ pull_request:
13
+ paths:
14
+ - "docs-site/**"
15
+ - "shipzil/**"
16
+ - "AGENTS.md"
17
+ workflow_dispatch:
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ # Always build, on every branch and PR. Type generation, static generation and
24
+ # the agent-output checks all run before anything can be deployed.
25
+ build:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v7
29
+ - uses: actions/setup-node@v7
30
+ with:
31
+ node-version: 22
32
+ cache: npm
33
+ cache-dependency-path: docs-site/package-lock.json
34
+ # setup-uv publishes no moving major tag, so this is pinned exactly.
35
+ - uses: astral-sh/setup-uv@v10.1.0
36
+ with:
37
+ enable-cache: true
38
+ - run: npm ci
39
+ working-directory: docs-site
40
+ - run: npm run types:check
41
+ working-directory: docs-site
42
+ - run: npm run build
43
+ working-directory: docs-site
44
+ - run: npm run check:agent
45
+ working-directory: docs-site
46
+ - uses: actions/upload-pages-artifact@v5
47
+ if: github.ref == 'refs/heads/main'
48
+ with:
49
+ path: docs-site/out
50
+
51
+ # Deploy only from main, and only once Pages is actually enabled. Pages on a
52
+ # private repository needs a paid plan, and deploy-pages returns 404 until the
53
+ # source is set to "GitHub Actions", which would fail every push.
54
+ # Enable with: gh variable set PAGES_ENABLED --body true
55
+ deploy:
56
+ if: github.ref == 'refs/heads/main' && vars.PAGES_ENABLED == 'true'
57
+ needs: build
58
+ runs-on: ubuntu-latest
59
+ permissions:
60
+ pages: write
61
+ id-token: write
62
+ environment:
63
+ name: github-pages
64
+ url: ${{ steps.deploy.outputs.page_url }}
65
+ steps:
66
+ - id: deploy
67
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,78 @@
1
+ name: release
2
+
3
+ # Publishes to PyPI when a version tag is pushed.
4
+ #
5
+ # Uses PyPI Trusted Publishing (OIDC), so there is no API token in this repository.
6
+ # One-time setup on PyPI: add a trusted publisher for this repo with workflow
7
+ # name "release" and environment "pypi".
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ # Never publish something that does not pass its own suite.
18
+ verify:
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: true
22
+ matrix:
23
+ python-version: ["3.10", "3.14"]
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+ # setup-uv publishes no moving major tag, so this is pinned exactly.
27
+ - uses: astral-sh/setup-uv@v10.1.0
28
+ with:
29
+ enable-cache: true
30
+ - run: uv run --python ${{ matrix.python-version }} --isolated --with pytest pytest -m "not live"
31
+
32
+ build:
33
+ needs: verify
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ # setup-uv publishes no moving major tag, so this is pinned exactly.
38
+ - uses: astral-sh/setup-uv@v10.1.0
39
+
40
+ - name: Fail if the tag does not match the package version
41
+ run: |
42
+ tag="${GITHUB_REF_NAME#v}"
43
+ pkg="$(uv run python -c 'import shipzil; print(shipzil.__version__)')"
44
+ echo "tag=$tag package=$pkg"
45
+ if [ "$tag" != "$pkg" ]; then
46
+ echo "::error::tag $tag does not match shipzil.__version__ $pkg"
47
+ exit 1
48
+ fi
49
+ if: github.event_name == 'push'
50
+
51
+ - run: uv build
52
+
53
+ - name: Check the wheel ships what it must
54
+ run: |
55
+ uv run --with twine twine check dist/*
56
+ python -m zipfile -l dist/*.whl > contents.txt
57
+ for required in shipzil/gateway.py shipzil/services.py shipzil/py.typed; do
58
+ grep -q "$required" contents.txt || {
59
+ echo "::error::wheel is missing $required"; exit 1; }
60
+ done
61
+
62
+ - uses: actions/upload-artifact@v7
63
+ with:
64
+ name: dist
65
+ path: dist/
66
+
67
+ publish:
68
+ needs: build
69
+ runs-on: ubuntu-latest
70
+ environment: pypi
71
+ permissions:
72
+ id-token: write # required for Trusted Publishing
73
+ steps:
74
+ - uses: actions/download-artifact@v8
75
+ with:
76
+ name: dist
77
+ path: dist/
78
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,22 @@
1
+ .env
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .probe/
10
+
11
+ # Scraped provider API docs. Reference only, never committed.
12
+ .apidocs/
13
+
14
+ # Fumadocs build and install output, never committed.
15
+ docs-site/node_modules/
16
+ docs-site/.next/
17
+ docs-site/out/
18
+
19
+ # Local build playbook — tricks, scrape recipes, credential inventory, open
20
+ # operational state. Deliberately never pushed. Must not move into the docs app.
21
+ NOTES.local.md
22
+ *.local.md
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,85 @@
1
+ # Agent guide
2
+
3
+ This repository is the Python shipping Gateway, not the separate Shipzil status
4
+ page repository.
5
+
6
+ ## Current scope
7
+
8
+ Supported provider adapters:
9
+
10
+ - `shippo`
11
+ - `easyship`
12
+ - `shipstation_v1`
13
+ - `shipstation_v2`
14
+
15
+ `Gateway` sits above the existing single-provider `Client`:
16
+
17
+ - no provider preference means query all configured sources
18
+ - an explicit `fallback=(...)` is caller-authored policy
19
+ - provider, carrier and exact `ServiceKey` filters intersect
20
+ - source failures remain in `GatewayQuote.sources`
21
+ - every Gateway rate carries its configured `source`
22
+ - buying and voiding use that exact source
23
+
24
+ Do not add health-aware routing, cheapest-rate selection, service equivalence,
25
+ manifest generation or automatic purchase fallback. Those are not Gateway MVP
26
+ features.
27
+
28
+ ## Code map
29
+
30
+ | Area | File | Role |
31
+ |---|---|---|
32
+ | internal source client | `shipzil/_client.py` | rate, buy, void for one adapter |
33
+ | Gateway | `shipzil/gateway.py` | multi-source aggregation and explicit filters |
34
+ | service map | `shipzil/services.py` | `ServiceKey`, `ServiceInfo`, `ServiceMap` |
35
+ | provider contract | `shipzil/providers/base.py` | adapter capabilities and fidelity gaps |
36
+ | provider adapters | `shipzil/providers/` | provider-specific requests/parsers |
37
+ | provider errors | `shipzil/normalize.py` | prose/code to `ExclusionCode` |
38
+ | live-shaped fixtures | `tests/fixtures/` | scrubbed provider responses |
39
+ | provider source guide | `docs-site/content/docs/provider-sources.md` | authoritative documentation URLs and verification rules |
40
+
41
+ ## Invariants
42
+
43
+ - `Item.value` and `Item.weight` are per unit.
44
+ - Provider machine keys go into `ServiceKey.service`; display names stay on
45
+ `Rate.service`.
46
+ - `ServiceKey` is provider-namespaced. Do not infer that two providers' keys are
47
+ equivalent.
48
+ - `Rate.source` is the configured account/source that produced the rate. Never
49
+ purchase through another source.
50
+ - `Quote.excluded` retains provider-reported and local exclusions. It cannot explain
51
+ services the provider silently omitted.
52
+ - Purchases are never retried automatically.
53
+ - A fan-out rate is an estimate, not one provider-native multi-parcel quote, and
54
+ cannot be bought as one label.
55
+ - Do not add a provider capability to `hazmat_fields` unless the adapter emits
56
+ that field on the wire.
57
+
58
+ ## Verification
59
+
60
+ ```bash
61
+ make check
62
+ make check-compat
63
+ make docs-build
64
+ make test-live
65
+ ```
66
+
67
+ `make test-live` loads the ignored `.env`. Live calls can buy test labels or
68
+ consume sandbox quota; inspect the marker and provider before running them.
69
+
70
+ When changing a provider parser, use a sanitized provider response plus a
71
+ payload-level test. A constructed fixture is appropriate for an edge case absent
72
+ from the captured response.
73
+
74
+ ## Provider documentation
75
+
76
+ Read `docs-site/content/docs/provider-sources.md` before changing provider
77
+ behavior. Verify claims against the provider's current schema or documentation and
78
+ then add a captured-response or payload-level test. Never commit `.env`, raw
79
+ credentials or unsanitized provider payloads.
80
+
81
+ ## Style
82
+
83
+ Keep the public API small and Pythonic. Prefer a dataclass, tuple, mapping or
84
+ keyword argument over a hierarchy of policy/configuration classes. Use
85
+ `dataclasses.replace` when copying a model so new fields cannot silently vanish.
@@ -0,0 +1,77 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-09-19
4
+
5
+ First published release. Alpha: the interface may change before 1.0.
6
+
7
+ ### Added
8
+
9
+ - Multi-source `Gateway` with direct credential configuration.
10
+ - Concurrent source rating and concurrent per-parcel FANOUT.
11
+ - Source provenance on rates and labels.
12
+ - Provider, carrier and exact-service filtering.
13
+ - `GatewayQuote` sequence operations, diagnostics, messages, `cheapest` and
14
+ `fastest`.
15
+ - Structured exclusions for locally filtered and unaddressable rates.
16
+ - Replaceable HTTP `Transport` protocol.
17
+ - Fumadocs site, generated Python reference and agent-readable Markdown routes.
18
+ - CI checks for Python 3.10 through 3.14 and a trusted-publishing release workflow.
19
+
20
+ ### Changed
21
+
22
+ - `get_rates(sources=...)` selects configured source names; `providers=` now selects
23
+ adapter types only. The single overloaded argument matched either, which hid a
24
+ caller's typo as an empty result. An unknown name in either raises
25
+ `ConfigurationError`.
26
+ - `max_spend` accepts `max_spend_currency`. A rate with no currency, or one in a
27
+ different currency, is refused rather than compared, because shipzil does not
28
+ convert money.
29
+ - Removed `Label.parcel_labels` and `TrackingLeg.piece`. No adapter populated
30
+ either, and a documented field that is always empty is a promise the library does
31
+ not keep.
32
+
33
+ - Minimum Python version is 3.10.
34
+ - `Gateway` is the caller entry point; the single-source client is internal.
35
+ - `Rate` and `Label` constructors are keyword-only.
36
+ - `Capabilities` is a frozen dataclass.
37
+ - `SourceResult` exposes rates, exclusions and messages directly.
38
+ - `cheapest` returns `None` for mixed or unknown currencies.
39
+ - Cross-border rating stops before a provider call when any item lacks weight or
40
+ value.
41
+ - Easyship requires explicit items and values; it no longer creates placeholder
42
+ merchandise or a default customs value.
43
+ - ShipStation v1 label bytes are available in `Label.label_data`.
44
+ - Purchase-path `ProviderError` becomes `AmbiguousPurchaseError`.
45
+
46
+ ### Fixed
47
+
48
+ - A rate list shortened by a transient carrier failure is now reported. Measured
49
+ against a Shippo test account on a US domestic lane, a rating call returns 11
50
+ rates normally and 3 when UPS answers "Hard: Too Many Requests". The reason was
51
+ present in the response messages and already classifiable, but no exclusion was
52
+ produced because the list was not empty, so a caller could lose 8 of 11 options
53
+ with no signal. Structural account and lane messages are still not promoted;
54
+ they are permanent and would be noise on every quote.
55
+
56
+ - Carrier family filters now match variants such as DHL Express and DHL eCommerce
57
+ without matching unrelated carriers.
58
+ - Rates removed by shipzil filters return an exclusion instead of disappearing.
59
+ - Provider warning messages survive Gateway aggregation.
60
+ - Provider counts, customs value basis and idempotency documentation were reconciled
61
+ with current source and provider schemas.
62
+
63
+ ### Verified
64
+
65
+ - Live rating now covers all four adapters. Easyship runs against its sandbox host;
66
+ ShipStation v1 rating is read-only and confirmed live that it returns no currency
67
+ and no delivery estimate.
68
+ - Partial-shortfall reporting was checked in every adapter rather than assumed:
69
+ ShipStation v2 and v1 already reported failures alongside returned rates, and
70
+ Easyship's response carries no failure field to report.
71
+
72
+ - Offline tests cover all four adapters with sanitized responses and payload
73
+ assertions.
74
+ - Live rating covers Shippo and ShipStation v2, including a two-source Gateway
75
+ call with provenance checks.
76
+ - Shippo test purchase and refund are exercised with a `shippo_test_` token.
77
+ - ShipStation v2 purchase and void remain unverified live.