python-sendparcel-inpost 0.1.1__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_sendparcel_inpost-0.1.1/.gitignore +34 -0
- python_sendparcel_inpost-0.1.1/CHANGELOG.md +23 -0
- python_sendparcel_inpost-0.1.1/CONTRIBUTING.md +73 -0
- python_sendparcel_inpost-0.1.1/PKG-INFO +371 -0
- python_sendparcel_inpost-0.1.1/README.md +343 -0
- python_sendparcel_inpost-0.1.1/docs/api.md +74 -0
- python_sendparcel_inpost-0.1.1/docs/configuration.md +235 -0
- python_sendparcel_inpost-0.1.1/docs/index.md +13 -0
- python_sendparcel_inpost-0.1.1/docs/quickstart.md +167 -0
- python_sendparcel_inpost-0.1.1/pyproject.toml +79 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/__init__.py +14 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/client.py +177 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/enums.py +18 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/exceptions.py +45 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/providers/__init__.py +6 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/providers/courier.py +305 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/providers/locker.py +320 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/status_mapping.py +46 -0
- python_sendparcel_inpost-0.1.1/src/sendparcel_inpost/types.py +72 -0
- python_sendparcel_inpost-0.1.1/tests/__init__.py +0 -0
- python_sendparcel_inpost-0.1.1/tests/conftest.py +17 -0
- python_sendparcel_inpost-0.1.1/tests/test_client.py +233 -0
- python_sendparcel_inpost-0.1.1/tests/test_config_schema.py +44 -0
- python_sendparcel_inpost-0.1.1/tests/test_courier_provider.py +212 -0
- python_sendparcel_inpost-0.1.1/tests/test_entry_points.py +33 -0
- python_sendparcel_inpost-0.1.1/tests/test_enums.py +25 -0
- python_sendparcel_inpost-0.1.1/tests/test_exceptions.py +59 -0
- python_sendparcel_inpost-0.1.1/tests/test_locker_provider.py +317 -0
- python_sendparcel_inpost-0.1.1/tests/test_status_mapping.py +57 -0
- python_sendparcel_inpost-0.1.1/tests/test_types.py +94 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python bytecode
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
|
|
11
|
+
# Tool caches
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.ty/
|
|
16
|
+
.coverage
|
|
17
|
+
coverage.xml
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# Build artifacts
|
|
21
|
+
build/
|
|
22
|
+
dist/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
|
|
25
|
+
# IDE
|
|
26
|
+
.vscode/
|
|
27
|
+
.idea/
|
|
28
|
+
.sisyphus/
|
|
29
|
+
|
|
30
|
+
# OS
|
|
31
|
+
.DS_Store
|
|
32
|
+
|
|
33
|
+
# uv
|
|
34
|
+
uv.lock
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-02-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- InPost ShipX provider for python-sendparcel
|
|
13
|
+
- `InPostLockerProvider` for Paczkomat locker deliveries
|
|
14
|
+
- `InPostCourierProvider` for door-to-door courier deliveries
|
|
15
|
+
- `ShipXClient` standalone async HTTP client for the ShipX API
|
|
16
|
+
- ShipX exception hierarchy (`ShipXAPIError`, `ShipXAuthenticationError`, `ShipXValidationError`)
|
|
17
|
+
- ShipX-specific enums (`ShipXService`, `ShipXParcelTemplate`)
|
|
18
|
+
- ShipX-specific TypedDicts (`ShipXAddress`, `ShipXPeer`, `ShipXParcel`, `ShipXShipmentPayload`)
|
|
19
|
+
- Status mapping from 24 ShipX statuses to 8 sendparcel statuses
|
|
20
|
+
- Webhook verification by InPost source IP range (`91.216.25.0/24`)
|
|
21
|
+
- Address conversion with legacy name-splitting fallback
|
|
22
|
+
- Entry-point registration for auto-discovery
|
|
23
|
+
- Full test suite (93 tests)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Contributing to python-sendparcel-inpost
|
|
2
|
+
|
|
3
|
+
Thank you for considering a contribution to `python-sendparcel-inpost` — the
|
|
4
|
+
InPost ShipX provider for the sendparcel ecosystem.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- Python 3.12 or later
|
|
9
|
+
- [uv](https://docs.astral.sh/uv/) package manager
|
|
10
|
+
|
|
11
|
+
## Development setup
|
|
12
|
+
|
|
13
|
+
1. Clone the repository and navigate to the `python-sendparcel-inpost` directory.
|
|
14
|
+
2. Install the project with dev dependencies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv sync --extra dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running tests
|
|
21
|
+
|
|
22
|
+
Tests use **pytest** with **pytest-asyncio** (`asyncio_mode = "auto"`) and
|
|
23
|
+
**respx** for HTTP mocking.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv run pytest tests/ -q
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Always run tests through `uv run` so the correct virtualenv is used.
|
|
30
|
+
|
|
31
|
+
## Linting and formatting
|
|
32
|
+
|
|
33
|
+
The project uses **ruff** for both linting and formatting:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv run ruff check src tests
|
|
37
|
+
uv run ruff format --check src tests
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Code style
|
|
41
|
+
|
|
42
|
+
- All code, comments, docstrings, and messages **must be in English**.
|
|
43
|
+
- Keep APIs **async-first**.
|
|
44
|
+
- Use `anyio` for async primitives and async/sync bridging points.
|
|
45
|
+
- Imports belong at the **top of the file** (PEP 8). Inline imports are
|
|
46
|
+
only acceptable to break a verified circular import, with a comment
|
|
47
|
+
explaining the reason.
|
|
48
|
+
- Follow the ruff rule set configured in `pyproject.toml`
|
|
49
|
+
(`E`, `W`, `F`, `I`, `N`, `UP`, `B`, `A`, `SIM`, `RUF`).
|
|
50
|
+
|
|
51
|
+
## Pull request process
|
|
52
|
+
|
|
53
|
+
1. Fork the repository and create a feature branch from `main`.
|
|
54
|
+
2. Make your changes in small, focused commits.
|
|
55
|
+
3. Ensure all quality checks pass (tests, linting, formatting).
|
|
56
|
+
4. Open a pull request against `main` with a clear description of your
|
|
57
|
+
changes.
|
|
58
|
+
|
|
59
|
+
## Commit messages
|
|
60
|
+
|
|
61
|
+
- Use the **imperative mood** ("Add feature", not "Added feature").
|
|
62
|
+
- Keep the subject line concise (72 characters or fewer).
|
|
63
|
+
- Reference related issues when applicable (e.g., `Fix #42`).
|
|
64
|
+
|
|
65
|
+
## Ecosystem rules
|
|
66
|
+
|
|
67
|
+
- Keep APIs async-first.
|
|
68
|
+
- Use `anyio` for async primitives and async/sync bridging points.
|
|
69
|
+
- Preserve plugin compatibility with `python-sendparcel` core contracts —
|
|
70
|
+
this provider must conform to the interfaces defined in the core package.
|
|
71
|
+
- Test against the editable local core (`python-sendparcel`) to catch
|
|
72
|
+
breaking changes early.
|
|
73
|
+
- Use `httpx` for HTTP communication, `respx` for HTTP mocking in tests.
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-sendparcel-inpost
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: InPost ShipX provider for python-sendparcel.
|
|
5
|
+
Author-email: Dominik Kozaczko <dominik@kozaczko.info>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: inpost,parcel,sendparcel,shipping,shipx
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: anyio>=4.0
|
|
19
|
+
Requires-Dist: httpx>=0.27.0
|
|
20
|
+
Requires-Dist: python-sendparcel>=0.1.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: respx>=0.22.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# python-sendparcel-inpost
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/python-sendparcel-inpost/)
|
|
32
|
+
[](https://pypi.org/project/python-sendparcel-inpost/)
|
|
33
|
+
[](https://github.com/python-sendparcel/python-sendparcel-inpost/blob/main/LICENSE)
|
|
34
|
+
|
|
35
|
+
InPost ShipX API provider for the [python-sendparcel](https://github.com/python-sendparcel/python-sendparcel) shipping ecosystem.
|
|
36
|
+
|
|
37
|
+
> **Alpha (0.1.0)** — API may change between minor releases. Pin your dependency if you use it in production.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Two providers** — `InPostLockerProvider` (Paczkomat locker) and `InPostCourierProvider` (door-to-door courier) as separate `BaseProvider` subclasses.
|
|
42
|
+
- **Standalone ShipX client** — `ShipXClient` async HTTP wrapper usable independently of the sendparcel framework.
|
|
43
|
+
- **Auto-discovery** — both providers register via the `sendparcel.providers` entry-point group; no manual registration needed.
|
|
44
|
+
- **Status mapping** — 24 ShipX statuses mapped to 8 sendparcel lifecycle states.
|
|
45
|
+
- **Webhook support** — callback verification by InPost source IP range (`91.216.25.0/24`).
|
|
46
|
+
- **Address conversion** — automatic conversion between sendparcel `AddressInfo` and ShipX peer format, with legacy name-splitting fallback.
|
|
47
|
+
- **Structured error handling** — `ShipXAPIError` hierarchy inheriting from core `CommunicationError` with status codes and validation details.
|
|
48
|
+
- **Async-first** — fully asynchronous with `httpx` and `anyio`.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv add python-sendparcel-inpost
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or with pip:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install python-sendparcel-inpost
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Both providers are auto-discovered via the `sendparcel.providers` entry-point group — no manual registration needed.
|
|
63
|
+
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
### Using providers through sendparcel
|
|
67
|
+
|
|
68
|
+
The providers integrate with the `sendparcel` flow automatically:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from sendparcel.registry import PluginRegistry
|
|
72
|
+
|
|
73
|
+
# Providers are discovered via entry points
|
|
74
|
+
registry = PluginRegistry()
|
|
75
|
+
choices = registry.get_choices()
|
|
76
|
+
# [('inpost_locker', 'InPost Paczkomat'), ('inpost_courier', 'InPost Kurier'), ...]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Creating a locker shipment
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
provider = InPostLockerProvider(shipment=shipment, config={
|
|
83
|
+
"token": "your-shipx-api-token",
|
|
84
|
+
"organization_id": 12345,
|
|
85
|
+
"sandbox": True, # use sandbox for testing
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
result = await provider.create_shipment(
|
|
89
|
+
target_point="KRA010", # required: locker machine ID
|
|
90
|
+
parcel_template="small", # optional: "small", "medium", "large"
|
|
91
|
+
sending_method="dispatch_order", # optional
|
|
92
|
+
)
|
|
93
|
+
# result["external_id"] = "123456789"
|
|
94
|
+
# result["tracking_number"] = "6100..."
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Creating a courier shipment
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
provider = InPostCourierProvider(shipment=shipment, config={
|
|
101
|
+
"token": "your-shipx-api-token",
|
|
102
|
+
"organization_id": 12345,
|
|
103
|
+
"sandbox": True,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
result = await provider.create_shipment()
|
|
107
|
+
# Parcels are passed as explicit parameters to create_shipment()
|
|
108
|
+
# Dimensions are converted from cm to mm automatically
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Using ShipXClient standalone
|
|
112
|
+
|
|
113
|
+
The HTTP client can be used independently of the sendparcel framework:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from sendparcel_inpost import ShipXClient
|
|
117
|
+
|
|
118
|
+
async with ShipXClient(
|
|
119
|
+
token="your-token",
|
|
120
|
+
organization_id=12345,
|
|
121
|
+
sandbox=True,
|
|
122
|
+
) as client:
|
|
123
|
+
# Create shipment
|
|
124
|
+
result = await client.create_shipment(payload={
|
|
125
|
+
"receiver": {
|
|
126
|
+
"first_name": "Jan",
|
|
127
|
+
"last_name": "Kowalski",
|
|
128
|
+
"phone": "500100200",
|
|
129
|
+
"email": "jan@example.com",
|
|
130
|
+
},
|
|
131
|
+
"parcels": [{"template": "small"}],
|
|
132
|
+
"service": "inpost_locker_standard",
|
|
133
|
+
"custom_attributes": {
|
|
134
|
+
"target_point": "KRA010",
|
|
135
|
+
"sending_method": "dispatch_order",
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
# Get shipment details
|
|
140
|
+
shipment = await client.get_shipment(result["id"])
|
|
141
|
+
|
|
142
|
+
# Download label
|
|
143
|
+
label_pdf = await client.get_label(result["id"])
|
|
144
|
+
|
|
145
|
+
# Track (public, no auth required)
|
|
146
|
+
tracking = await client.get_tracking("6100123456789")
|
|
147
|
+
|
|
148
|
+
# Cancel (only for created/offers_prepared/offer_selected statuses)
|
|
149
|
+
await client.cancel_shipment(result["id"])
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
Provider configuration is passed as a dict either through the `config` constructor parameter or via your framework adapter's settings:
|
|
155
|
+
|
|
156
|
+
| Key | Type | Default | Description |
|
|
157
|
+
|---|---|---|---|
|
|
158
|
+
| `token` | `str` | *(required)* | ShipX API bearer token |
|
|
159
|
+
| `organization_id` | `int` | *(required)* | ShipX organization ID |
|
|
160
|
+
| `sandbox` | `bool` | `False` | Use sandbox API endpoint |
|
|
161
|
+
| `base_url` | `str` | `None` | Override API base URL (takes precedence over `sandbox`) |
|
|
162
|
+
| `timeout` | `float` | `30.0` | HTTP request timeout in seconds |
|
|
163
|
+
|
|
164
|
+
### API endpoints
|
|
165
|
+
|
|
166
|
+
| Environment | Base URL |
|
|
167
|
+
|---|---|
|
|
168
|
+
| Production | `https://api-shipx-pl.easypack24.net` |
|
|
169
|
+
| Sandbox | `https://sandbox-api-shipx-pl.easypack24.net` |
|
|
170
|
+
|
|
171
|
+
### Integration with framework adapters
|
|
172
|
+
|
|
173
|
+
Pass InPost configuration through your adapter's provider settings:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Django settings.py
|
|
177
|
+
SENDPARCEL_PROVIDER_SETTINGS = {
|
|
178
|
+
"inpost_locker": {
|
|
179
|
+
"token": "your-shipx-token",
|
|
180
|
+
"organization_id": 12345,
|
|
181
|
+
"sandbox": True,
|
|
182
|
+
},
|
|
183
|
+
"inpost_courier": {
|
|
184
|
+
"token": "your-shipx-token",
|
|
185
|
+
"organization_id": 12345,
|
|
186
|
+
"sandbox": True,
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# FastAPI / Litestar
|
|
191
|
+
config = SendparcelConfig(
|
|
192
|
+
default_provider="inpost_locker",
|
|
193
|
+
providers={
|
|
194
|
+
"inpost_locker": {
|
|
195
|
+
"token": "your-shipx-token",
|
|
196
|
+
"organization_id": 12345,
|
|
197
|
+
"sandbox": True,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Providers
|
|
204
|
+
|
|
205
|
+
### InPostLockerProvider
|
|
206
|
+
|
|
207
|
+
Paczkomat locker delivery. The receiver picks up the parcel from a self-service locker machine.
|
|
208
|
+
|
|
209
|
+
- **Slug**: `inpost_locker`
|
|
210
|
+
- **Service**: `inpost_locker_standard`
|
|
211
|
+
- **Confirmation method**: PUSH (webhook-based)
|
|
212
|
+
- **Supported countries**: PL
|
|
213
|
+
|
|
214
|
+
**`create_shipment` parameters:**
|
|
215
|
+
|
|
216
|
+
| Parameter | Required | Description |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `target_point` | yes | Locker machine ID (e.g. `"KRA010"`) |
|
|
219
|
+
| `parcel_template` | no | Size: `"small"`, `"medium"`, or `"large"`. Auto-detected from parcel dimensions if omitted. |
|
|
220
|
+
| `sending_method` | no | Default: `"dispatch_order"` |
|
|
221
|
+
|
|
222
|
+
Parcel template auto-detection logic (based on height):
|
|
223
|
+
- height > 19 cm: `large`
|
|
224
|
+
- height > 8 cm: `medium`
|
|
225
|
+
- otherwise: `small`
|
|
226
|
+
|
|
227
|
+
### InPostCourierProvider
|
|
228
|
+
|
|
229
|
+
Door-to-door courier delivery.
|
|
230
|
+
|
|
231
|
+
- **Slug**: `inpost_courier`
|
|
232
|
+
- **Service**: `inpost_courier_standard`
|
|
233
|
+
- **Confirmation method**: PUSH (webhook-based)
|
|
234
|
+
- **Supported countries**: PL
|
|
235
|
+
|
|
236
|
+
Parcel dimensions are received as explicit `parcels` parameter and converted from cm to mm for the ShipX API. If no parcels are provided, a default 1 kg parcel is used.
|
|
237
|
+
|
|
238
|
+
### Common provider methods
|
|
239
|
+
|
|
240
|
+
Both providers implement the full `BaseProvider` interface:
|
|
241
|
+
|
|
242
|
+
| Method | Purpose |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `create_shipment(**kwargs)` | Create a shipment in ShipX |
|
|
245
|
+
| `create_label(**kwargs)` | Download shipping label (PDF by default) |
|
|
246
|
+
| `fetch_shipment_status(**kwargs)` | Poll ShipX API for current status |
|
|
247
|
+
| `cancel_shipment(**kwargs)` | Cancel the shipment (returns `True`/`False`) |
|
|
248
|
+
| `verify_callback(data, headers, **kwargs)` | Verify webhook source IP is in InPost's `91.216.25.0/24` range |
|
|
249
|
+
| `handle_callback(data, headers, **kwargs)` | Process webhook payload, map ShipX status to sendparcel status |
|
|
250
|
+
|
|
251
|
+
## Address Handling
|
|
252
|
+
|
|
253
|
+
The providers accept `sendparcel.types.AddressInfo` and convert it to the ShipX peer format. Two addressing styles are supported:
|
|
254
|
+
|
|
255
|
+
**InPost-style** (preferred):
|
|
256
|
+
```python
|
|
257
|
+
address: AddressInfo = {
|
|
258
|
+
"first_name": "Jan",
|
|
259
|
+
"last_name": "Kowalski",
|
|
260
|
+
"street": "Krakowska",
|
|
261
|
+
"building_number": "10",
|
|
262
|
+
"flat_number": "5",
|
|
263
|
+
"city": "Krakow",
|
|
264
|
+
"postal_code": "30-001",
|
|
265
|
+
"country_code": "PL",
|
|
266
|
+
"phone": "500100200",
|
|
267
|
+
"email": "jan@example.com",
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Legacy style** (auto-split):
|
|
272
|
+
```python
|
|
273
|
+
address: AddressInfo = {
|
|
274
|
+
"name": "Jan Kowalski", # split on first space -> first_name + last_name
|
|
275
|
+
"line1": "Krakowska 10/5", # used as street fallback
|
|
276
|
+
"city": "Krakow",
|
|
277
|
+
"postal_code": "30-001",
|
|
278
|
+
"phone": "500100200",
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Status Mapping
|
|
283
|
+
|
|
284
|
+
ShipX uses 24 internal statuses. These are mapped to 8 sendparcel statuses:
|
|
285
|
+
|
|
286
|
+
| sendparcel status | ShipX statuses |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `CREATED` | `created`, `offers_prepared`, `offer_selected` |
|
|
289
|
+
| `LABEL_READY` | `confirmed` |
|
|
290
|
+
| `IN_TRANSIT` | `dispatched_by_sender`, `collected_from_sender`, `taken_by_courier`, `adopted_at_source_branch`, `sent_from_source_branch`, `adopted_at_sorting_center` |
|
|
291
|
+
| `OUT_FOR_DELIVERY` | `out_for_delivery`, `ready_to_pickup`, `pickup_reminder_sent`, `avizo`, `stack_in_box_machine`, `stack_in_customer_service_point` |
|
|
292
|
+
| `DELIVERED` | `delivered` |
|
|
293
|
+
| `CANCELLED` | `canceled` |
|
|
294
|
+
| `RETURNED` | `returned_to_sender` |
|
|
295
|
+
| `FAILED` | `rejected_by_receiver`, `undelivered`, `oversized`, `missing`, `claim_created` |
|
|
296
|
+
|
|
297
|
+
## Error Handling
|
|
298
|
+
|
|
299
|
+
All ShipX API errors inherit from `sendparcel.exceptions.CommunicationError`:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from sendparcel_inpost.exceptions import (
|
|
303
|
+
ShipXAPIError, # base: any non-2xx response
|
|
304
|
+
ShipXAuthenticationError, # 401 Unauthorized
|
|
305
|
+
ShipXValidationError, # 422 Unprocessable Entity
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
result = await client.create_shipment(payload=payload)
|
|
310
|
+
except ShipXAuthenticationError:
|
|
311
|
+
# Invalid or expired token
|
|
312
|
+
...
|
|
313
|
+
except ShipXValidationError as exc:
|
|
314
|
+
# Payload validation failed
|
|
315
|
+
print(exc.detail) # human-readable message
|
|
316
|
+
print(exc.errors) # list of field-level error dicts from ShipX
|
|
317
|
+
except ShipXAPIError as exc:
|
|
318
|
+
# Other API error
|
|
319
|
+
print(exc.status_code, exc.detail)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Webhooks
|
|
323
|
+
|
|
324
|
+
Both providers support InPost webhook callbacks for real-time status updates.
|
|
325
|
+
|
|
326
|
+
**Verification**: Webhook source IP must be in the `91.216.25.0/24` range. The IP is read from the `X-Forwarded-For` header (first entry). Invalid or missing IPs raise `sendparcel.exceptions.InvalidCallbackError`.
|
|
327
|
+
|
|
328
|
+
**Payload format** (expected from InPost):
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"payload": {
|
|
332
|
+
"shipment_id": 123456,
|
|
333
|
+
"status": "delivered"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Supported Versions
|
|
339
|
+
|
|
340
|
+
| Dependency | Version |
|
|
341
|
+
|---|---|
|
|
342
|
+
| Python | >= 3.12 |
|
|
343
|
+
| python-sendparcel | >= 0.1.0 |
|
|
344
|
+
| httpx | >= 0.27.0 |
|
|
345
|
+
| anyio | >= 4.0 |
|
|
346
|
+
|
|
347
|
+
## Running Tests
|
|
348
|
+
|
|
349
|
+
The test suite uses **pytest** with **pytest-asyncio** (`asyncio_mode = "auto"`)
|
|
350
|
+
and **respx** for HTTP mocking.
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# Install dev dependencies
|
|
354
|
+
uv sync --extra dev
|
|
355
|
+
|
|
356
|
+
# Run the full test suite
|
|
357
|
+
uv run pytest
|
|
358
|
+
|
|
359
|
+
# With coverage
|
|
360
|
+
uv run pytest --cov=sendparcel_inpost --cov-report=term-missing
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Credits
|
|
364
|
+
|
|
365
|
+
- **Author**: Dominik Kozaczko ([dominik@kozaczko.info](mailto:dominik@kozaczko.info))
|
|
366
|
+
- Built on top of [python-sendparcel](https://github.com/python-sendparcel/python-sendparcel) core library
|
|
367
|
+
- Integrates with the [InPost ShipX API](https://docs.inpost24.com/)
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
[MIT](https://github.com/python-sendparcel/python-sendparcel-inpost/blob/main/LICENSE)
|