python-sendparcel-inpost 0.1.1__tar.gz → 0.1.2__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.2/PKG-INFO +100 -0
- python_sendparcel_inpost-0.1.2/README.md +72 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/docs/configuration.md +6 -6
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/pyproject.toml +2 -2
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/providers/courier.py +16 -13
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/providers/locker.py +17 -15
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/status_mapping.py +17 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_courier_provider.py +26 -3
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_enums.py +11 -5
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_locker_provider.py +43 -17
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_status_mapping.py +31 -0
- python_sendparcel_inpost-0.1.1/PKG-INFO +0 -371
- python_sendparcel_inpost-0.1.1/README.md +0 -343
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/.gitignore +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/CHANGELOG.md +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/CONTRIBUTING.md +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/docs/api.md +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/docs/index.md +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/docs/quickstart.md +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/__init__.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/client.py +1 -1
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/enums.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/exceptions.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/providers/__init__.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/src/sendparcel_inpost/types.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/__init__.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/conftest.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_client.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_config_schema.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_entry_points.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_exceptions.py +0 -0
- {python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_types.py +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-sendparcel-inpost
|
|
3
|
+
Version: 0.1.2
|
|
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.1
|
|
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
|
+
InPost ShipX provider package for the `python-sendparcel` ecosystem.
|
|
32
|
+
|
|
33
|
+
> Alpha notice: this package tracks the still-changing `python-sendparcel` core.
|
|
34
|
+
|
|
35
|
+
## What it provides
|
|
36
|
+
|
|
37
|
+
- `InPostLockerProvider` for locker shipments
|
|
38
|
+
- `InPostCourierProvider` for courier shipments
|
|
39
|
+
- `ShipXClient` for direct async ShipX API access
|
|
40
|
+
- ShipX-to-sendparcel status normalization helpers
|
|
41
|
+
|
|
42
|
+
## Contract
|
|
43
|
+
|
|
44
|
+
This package follows the cleaned core contract:
|
|
45
|
+
|
|
46
|
+
- `create_shipment(...) -> ShipmentCreateResult`
|
|
47
|
+
- `create_label(...) -> LabelInfo`
|
|
48
|
+
- `handle_callback(...) -> ShipmentUpdateResult`
|
|
49
|
+
- `fetch_shipment_status(...) -> ShipmentUpdateResult`
|
|
50
|
+
- `cancel_shipment(...) -> bool`
|
|
51
|
+
|
|
52
|
+
Providers do not mutate shipment state directly. They translate ShipX responses into normalized results that the core flow applies.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv add python-sendparcel-inpost
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
or:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install python-sendparcel-inpost
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
| Key | Type | Description |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `token` | `str` | ShipX API bearer token |
|
|
71
|
+
| `organization_id` | `int` | ShipX organization ID |
|
|
72
|
+
| `sandbox` | `bool` | Use sandbox API |
|
|
73
|
+
| `base_url` | `str` | Optional API base override |
|
|
74
|
+
| `timeout` | `float` | Request timeout in seconds |
|
|
75
|
+
|
|
76
|
+
## Status normalization
|
|
77
|
+
|
|
78
|
+
ShipX statuses are normalized to sendparcel shipment statuses.
|
|
79
|
+
|
|
80
|
+
- recognized ShipX statuses produce `{"status": ...}`
|
|
81
|
+
- tracking numbers are included when available
|
|
82
|
+
- unknown ShipX statuses do not invent fake sendparcel statuses
|
|
83
|
+
|
|
84
|
+
That means callback and polling updates can safely return only tracking data when ShipX introduces a new status the mapper does not know yet.
|
|
85
|
+
|
|
86
|
+
## Labels
|
|
87
|
+
|
|
88
|
+
Labels are returned as payloads.
|
|
89
|
+
|
|
90
|
+
- PDF labels are returned as base64 content in `LabelInfo["content_base64"]`
|
|
91
|
+
- no label URL is persisted by the core contract
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv sync --extra dev
|
|
97
|
+
uv run pytest
|
|
98
|
+
uv run ruff check src tests
|
|
99
|
+
uv run mypy src tests
|
|
100
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# python-sendparcel-inpost
|
|
2
|
+
|
|
3
|
+
InPost ShipX provider package for the `python-sendparcel` ecosystem.
|
|
4
|
+
|
|
5
|
+
> Alpha notice: this package tracks the still-changing `python-sendparcel` core.
|
|
6
|
+
|
|
7
|
+
## What it provides
|
|
8
|
+
|
|
9
|
+
- `InPostLockerProvider` for locker shipments
|
|
10
|
+
- `InPostCourierProvider` for courier shipments
|
|
11
|
+
- `ShipXClient` for direct async ShipX API access
|
|
12
|
+
- ShipX-to-sendparcel status normalization helpers
|
|
13
|
+
|
|
14
|
+
## Contract
|
|
15
|
+
|
|
16
|
+
This package follows the cleaned core contract:
|
|
17
|
+
|
|
18
|
+
- `create_shipment(...) -> ShipmentCreateResult`
|
|
19
|
+
- `create_label(...) -> LabelInfo`
|
|
20
|
+
- `handle_callback(...) -> ShipmentUpdateResult`
|
|
21
|
+
- `fetch_shipment_status(...) -> ShipmentUpdateResult`
|
|
22
|
+
- `cancel_shipment(...) -> bool`
|
|
23
|
+
|
|
24
|
+
Providers do not mutate shipment state directly. They translate ShipX responses into normalized results that the core flow applies.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv add python-sendparcel-inpost
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
or:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install python-sendparcel-inpost
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
| Key | Type | Description |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `token` | `str` | ShipX API bearer token |
|
|
43
|
+
| `organization_id` | `int` | ShipX organization ID |
|
|
44
|
+
| `sandbox` | `bool` | Use sandbox API |
|
|
45
|
+
| `base_url` | `str` | Optional API base override |
|
|
46
|
+
| `timeout` | `float` | Request timeout in seconds |
|
|
47
|
+
|
|
48
|
+
## Status normalization
|
|
49
|
+
|
|
50
|
+
ShipX statuses are normalized to sendparcel shipment statuses.
|
|
51
|
+
|
|
52
|
+
- recognized ShipX statuses produce `{"status": ...}`
|
|
53
|
+
- tracking numbers are included when available
|
|
54
|
+
- unknown ShipX statuses do not invent fake sendparcel statuses
|
|
55
|
+
|
|
56
|
+
That means callback and polling updates can safely return only tracking data when ShipX introduces a new status the mapper does not know yet.
|
|
57
|
+
|
|
58
|
+
## Labels
|
|
59
|
+
|
|
60
|
+
Labels are returned as payloads.
|
|
61
|
+
|
|
62
|
+
- PDF labels are returned as base64 content in `LabelInfo["content_base64"]`
|
|
63
|
+
- no label URL is persisted by the core contract
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv sync --extra dev
|
|
69
|
+
uv run pytest
|
|
70
|
+
uv run ruff check src tests
|
|
71
|
+
uv run mypy src tests
|
|
72
|
+
```
|
|
@@ -80,10 +80,10 @@ Both providers implement the full `BaseProvider` interface:
|
|
|
80
80
|
|---|---|
|
|
81
81
|
| `create_shipment(**kwargs)` | Create a shipment in ShipX |
|
|
82
82
|
| `create_label(**kwargs)` | Download shipping label (PDF by default) |
|
|
83
|
-
| `fetch_shipment_status(**kwargs)` | Poll ShipX API
|
|
83
|
+
| `fetch_shipment_status(**kwargs)` | Poll ShipX API and return `ShipmentUpdateResult` |
|
|
84
84
|
| `cancel_shipment(**kwargs)` | Cancel the shipment (returns `True`/`False`) |
|
|
85
85
|
| `verify_callback(data, headers, **kwargs)` | Verify webhook source IP |
|
|
86
|
-
| `handle_callback(data, headers, **kwargs)` |
|
|
86
|
+
| `handle_callback(data, headers, **kwargs)` | Normalize webhook payload into `ShipmentUpdateResult` |
|
|
87
87
|
|
|
88
88
|
## ShipXClient
|
|
89
89
|
|
|
@@ -173,7 +173,8 @@ ShipX uses 24 internal statuses. These are mapped to 8 sendparcel statuses:
|
|
|
173
173
|
| `RETURNED` | `returned_to_sender` |
|
|
174
174
|
| `FAILED` | `rejected_by_receiver`, `undelivered`, `oversized`, `missing`, `claim_created` |
|
|
175
175
|
|
|
176
|
-
Unrecognized statuses return `None` from `map_shipx_status()`.
|
|
176
|
+
Unrecognized statuses return `None` from `map_shipx_status()`. Use
|
|
177
|
+
`build_shipment_update()` to keep tracking data even when a status is unknown.
|
|
177
178
|
|
|
178
179
|
## Error handling
|
|
179
180
|
|
|
@@ -206,9 +207,8 @@ the `X-Forwarded-For` header (first entry). Invalid or missing IPs raise
|
|
|
206
207
|
}
|
|
207
208
|
```
|
|
208
209
|
|
|
209
|
-
The `handle_callback` method
|
|
210
|
-
|
|
211
|
-
`ShipmentFlow`.
|
|
210
|
+
The `handle_callback` method normalizes ShipX payloads into
|
|
211
|
+
`ShipmentUpdateResult`. The core flow applies transitions.
|
|
212
212
|
|
|
213
213
|
## Enums
|
|
214
214
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-sendparcel-inpost"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "InPost ShipX provider for python-sendparcel."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -18,7 +18,7 @@ classifiers = [
|
|
|
18
18
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
19
|
"Typing :: Typed",
|
|
20
20
|
]
|
|
21
|
-
dependencies = ["python-sendparcel>=0.1.
|
|
21
|
+
dependencies = ["python-sendparcel>=0.1.1", "httpx>=0.27.0", "anyio>=4.0"]
|
|
22
22
|
|
|
23
23
|
[project.optional-dependencies]
|
|
24
24
|
dev = [
|
|
@@ -19,12 +19,12 @@ from sendparcel.types import (
|
|
|
19
19
|
LabelInfo,
|
|
20
20
|
ParcelInfo,
|
|
21
21
|
ShipmentCreateResult,
|
|
22
|
-
|
|
22
|
+
ShipmentUpdateResult,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
from sendparcel_inpost.client import ShipXClient
|
|
26
26
|
from sendparcel_inpost.exceptions import ShipXAPIError
|
|
27
|
-
from sendparcel_inpost.status_mapping import
|
|
27
|
+
from sendparcel_inpost.status_mapping import build_shipment_update
|
|
28
28
|
from sendparcel_inpost.types import ShipXAddress, ShipXPeer
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
@@ -256,23 +256,28 @@ class InPostCourierProvider(
|
|
|
256
256
|
data: dict[str, Any],
|
|
257
257
|
headers: dict[str, Any],
|
|
258
258
|
**kwargs: Any,
|
|
259
|
-
) ->
|
|
259
|
+
) -> ShipmentUpdateResult:
|
|
260
260
|
"""Process InPost webhook payload."""
|
|
261
261
|
payload = data.get("payload", {})
|
|
262
|
-
shipx_status = payload.get("status", "")
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
shipx_status = str(payload.get("status", ""))
|
|
263
|
+
tracking_number = str(payload.get("tracking_number", ""))
|
|
264
|
+
update = build_shipment_update(
|
|
265
|
+
shipx_status,
|
|
266
|
+
tracking_number=tracking_number,
|
|
267
|
+
)
|
|
268
|
+
if update:
|
|
265
269
|
logger.info(
|
|
266
270
|
"InPost webhook: %s -> %s (shipment %s)",
|
|
267
271
|
shipx_status,
|
|
268
|
-
|
|
272
|
+
update,
|
|
269
273
|
payload.get("shipment_id"),
|
|
270
274
|
)
|
|
275
|
+
return update
|
|
271
276
|
|
|
272
277
|
async def fetch_shipment_status(
|
|
273
278
|
self,
|
|
274
279
|
**kwargs: Any,
|
|
275
|
-
) ->
|
|
280
|
+
) -> ShipmentUpdateResult:
|
|
276
281
|
"""Fetch current status from ShipX API."""
|
|
277
282
|
shipment_id = int(self.shipment.external_id)
|
|
278
283
|
|
|
@@ -284,11 +289,9 @@ class InPostCourierProvider(
|
|
|
284
289
|
finally:
|
|
285
290
|
await client.close()
|
|
286
291
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return ShipmentStatusResponse(
|
|
291
|
-
status=sendparcel_status.value if sendparcel_status else None,
|
|
292
|
+
return build_shipment_update(
|
|
293
|
+
str(response.get("status", "")),
|
|
294
|
+
tracking_number=str(response.get("tracking_number", "")),
|
|
292
295
|
)
|
|
293
296
|
|
|
294
297
|
async def cancel_shipment(self, **kwargs: Any) -> bool:
|
|
@@ -19,12 +19,12 @@ from sendparcel.types import (
|
|
|
19
19
|
LabelInfo,
|
|
20
20
|
ParcelInfo,
|
|
21
21
|
ShipmentCreateResult,
|
|
22
|
-
|
|
22
|
+
ShipmentUpdateResult,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
from sendparcel_inpost.client import ShipXClient
|
|
26
26
|
from sendparcel_inpost.exceptions import ShipXAPIError
|
|
27
|
-
from sendparcel_inpost.status_mapping import
|
|
27
|
+
from sendparcel_inpost.status_mapping import build_shipment_update
|
|
28
28
|
from sendparcel_inpost.types import ShipXAddress, ShipXPeer
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
@@ -269,27 +269,31 @@ class InPostLockerProvider(
|
|
|
269
269
|
data: dict[str, Any],
|
|
270
270
|
headers: dict[str, Any],
|
|
271
271
|
**kwargs: Any,
|
|
272
|
-
) ->
|
|
272
|
+
) -> ShipmentUpdateResult:
|
|
273
273
|
"""Process InPost webhook payload.
|
|
274
274
|
|
|
275
|
-
The
|
|
276
|
-
This method extracts and normalizes the status.
|
|
275
|
+
The core flow applies transitions. Providers only normalize payloads.
|
|
277
276
|
"""
|
|
278
277
|
payload = data.get("payload", {})
|
|
279
|
-
shipx_status = payload.get("status", "")
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
shipx_status = str(payload.get("status", ""))
|
|
279
|
+
tracking_number = str(payload.get("tracking_number", ""))
|
|
280
|
+
update = build_shipment_update(
|
|
281
|
+
shipx_status,
|
|
282
|
+
tracking_number=tracking_number,
|
|
283
|
+
)
|
|
284
|
+
if update:
|
|
282
285
|
logger.info(
|
|
283
286
|
"InPost webhook: %s -> %s (shipment %s)",
|
|
284
287
|
shipx_status,
|
|
285
|
-
|
|
288
|
+
update,
|
|
286
289
|
payload.get("shipment_id"),
|
|
287
290
|
)
|
|
291
|
+
return update
|
|
288
292
|
|
|
289
293
|
async def fetch_shipment_status(
|
|
290
294
|
self,
|
|
291
295
|
**kwargs: Any,
|
|
292
|
-
) ->
|
|
296
|
+
) -> ShipmentUpdateResult:
|
|
293
297
|
"""Fetch current status from ShipX API."""
|
|
294
298
|
shipment_id = int(self.shipment.external_id)
|
|
295
299
|
|
|
@@ -299,11 +303,9 @@ class InPostLockerProvider(
|
|
|
299
303
|
finally:
|
|
300
304
|
await client.close()
|
|
301
305
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return ShipmentStatusResponse(
|
|
306
|
-
status=sendparcel_status.value if sendparcel_status else None,
|
|
306
|
+
return build_shipment_update(
|
|
307
|
+
str(response.get("status", "")),
|
|
308
|
+
tracking_number=str(response.get("tracking_number", "")),
|
|
307
309
|
)
|
|
308
310
|
|
|
309
311
|
async def cancel_shipment(self, **kwargs: Any) -> bool:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""ShipX status to sendparcel status mapping."""
|
|
2
2
|
|
|
3
3
|
from sendparcel.enums import ShipmentStatus
|
|
4
|
+
from sendparcel.types import ShipmentUpdateResult
|
|
4
5
|
|
|
5
6
|
SHIPX_TO_SENDPARCEL_STATUS: dict[str, ShipmentStatus] = {
|
|
6
7
|
# CREATED
|
|
@@ -44,3 +45,19 @@ def map_shipx_status(shipx_status: str) -> ShipmentStatus | None:
|
|
|
44
45
|
Returns None if the status is not recognized.
|
|
45
46
|
"""
|
|
46
47
|
return SHIPX_TO_SENDPARCEL_STATUS.get(shipx_status)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_shipment_update(
|
|
51
|
+
shipx_status: str,
|
|
52
|
+
*,
|
|
53
|
+
tracking_number: str = "",
|
|
54
|
+
) -> ShipmentUpdateResult:
|
|
55
|
+
"""Build a normalized sendparcel update from ShipX data."""
|
|
56
|
+
|
|
57
|
+
update: ShipmentUpdateResult = {}
|
|
58
|
+
mapped_status = map_shipx_status(shipx_status)
|
|
59
|
+
if mapped_status is not None:
|
|
60
|
+
update["status"] = mapped_status.value
|
|
61
|
+
if tracking_number:
|
|
62
|
+
update["tracking_number"] = tracking_number
|
|
63
|
+
return update
|
{python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_courier_provider.py
RENAMED
|
@@ -53,7 +53,6 @@ class _FakeShipment:
|
|
|
53
53
|
provider: str = "inpost_courier"
|
|
54
54
|
external_id: str = ""
|
|
55
55
|
tracking_number: str = ""
|
|
56
|
-
label_url: str = ""
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
class TestCourierProviderClassVars:
|
|
@@ -112,7 +111,7 @@ class TestCourierCreateShipment:
|
|
|
112
111
|
)
|
|
113
112
|
|
|
114
113
|
assert result["external_id"] == "888"
|
|
115
|
-
assert result
|
|
114
|
+
assert result.get("tracking_number") == "TRACK888"
|
|
116
115
|
|
|
117
116
|
call_kwargs = mock_client.create_shipment.call_args
|
|
118
117
|
payload = call_kwargs.kwargs["payload"]
|
|
@@ -203,10 +202,34 @@ class TestCourierFetchStatus:
|
|
|
203
202
|
return_value={
|
|
204
203
|
"id": 888,
|
|
205
204
|
"status": "taken_by_courier",
|
|
205
|
+
"tracking_number": "TRACK888",
|
|
206
206
|
},
|
|
207
207
|
)
|
|
208
208
|
mock_client.close = AsyncMock()
|
|
209
209
|
|
|
210
210
|
result = await provider.fetch_shipment_status()
|
|
211
211
|
|
|
212
|
-
assert result
|
|
212
|
+
assert result.get("status") == ShipmentStatus.IN_TRANSIT.value
|
|
213
|
+
assert result.get("tracking_number") == "TRACK888"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class TestCourierHandleCallback:
|
|
217
|
+
async def test_extracts_status_and_tracking_number(self) -> None:
|
|
218
|
+
shipment = _FakeShipment()
|
|
219
|
+
provider = InPostCourierProvider(shipment, config={})
|
|
220
|
+
|
|
221
|
+
update = await provider.handle_callback(
|
|
222
|
+
data={
|
|
223
|
+
"payload": {
|
|
224
|
+
"shipment_id": 888,
|
|
225
|
+
"status": "taken_by_courier",
|
|
226
|
+
"tracking_number": "TRACK888",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
headers={},
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
assert update == {
|
|
233
|
+
"status": ShipmentStatus.IN_TRANSIT.value,
|
|
234
|
+
"tracking_number": "TRACK888",
|
|
235
|
+
}
|
|
@@ -5,10 +5,16 @@ from sendparcel_inpost.enums import ShipXParcelTemplate, ShipXService
|
|
|
5
5
|
|
|
6
6
|
class TestShipXService:
|
|
7
7
|
def test_locker_standard(self) -> None:
|
|
8
|
-
assert
|
|
8
|
+
assert (
|
|
9
|
+
ShipXService.INPOST_LOCKER_STANDARD.value
|
|
10
|
+
== "inpost_locker_standard"
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
def test_inpost_courier_standard(self) -> None:
|
|
11
|
-
assert
|
|
14
|
+
assert (
|
|
15
|
+
ShipXService.INPOST_COURIER_STANDARD.value
|
|
16
|
+
== "inpost_courier_standard"
|
|
17
|
+
)
|
|
12
18
|
|
|
13
19
|
def test_is_str_enum(self) -> None:
|
|
14
20
|
assert isinstance(ShipXService.INPOST_LOCKER_STANDARD, str)
|
|
@@ -16,10 +22,10 @@ class TestShipXService:
|
|
|
16
22
|
|
|
17
23
|
class TestShipXParcelTemplate:
|
|
18
24
|
def test_small(self) -> None:
|
|
19
|
-
assert ShipXParcelTemplate.SMALL == "small"
|
|
25
|
+
assert ShipXParcelTemplate.SMALL.value == "small"
|
|
20
26
|
|
|
21
27
|
def test_medium(self) -> None:
|
|
22
|
-
assert ShipXParcelTemplate.MEDIUM == "medium"
|
|
28
|
+
assert ShipXParcelTemplate.MEDIUM.value == "medium"
|
|
23
29
|
|
|
24
30
|
def test_large(self) -> None:
|
|
25
|
-
assert ShipXParcelTemplate.LARGE == "large"
|
|
31
|
+
assert ShipXParcelTemplate.LARGE.value == "large"
|
{python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_locker_provider.py
RENAMED
|
@@ -45,7 +45,6 @@ class _FakeShipment:
|
|
|
45
45
|
provider: str = "inpost_locker"
|
|
46
46
|
external_id: str = ""
|
|
47
47
|
tracking_number: str = ""
|
|
48
|
-
label_url: str = ""
|
|
49
48
|
|
|
50
49
|
|
|
51
50
|
class TestLockerProviderClassVars:
|
|
@@ -105,7 +104,7 @@ class TestLockerCreateShipment:
|
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
assert result["external_id"] == "999"
|
|
108
|
-
assert result
|
|
107
|
+
assert result.get("tracking_number") == "TRACK999"
|
|
109
108
|
|
|
110
109
|
# Verify the payload sent to client
|
|
111
110
|
call_kwargs = mock_client.create_shipment.call_args
|
|
@@ -154,7 +153,7 @@ class TestLockerCreateLabel:
|
|
|
154
153
|
label = await provider.create_label()
|
|
155
154
|
|
|
156
155
|
assert label["format"] == "PDF"
|
|
157
|
-
assert label
|
|
156
|
+
assert label.get("content_base64") # non-empty base64 string
|
|
158
157
|
|
|
159
158
|
|
|
160
159
|
class TestLockerFetchStatus:
|
|
@@ -174,13 +173,18 @@ class TestLockerFetchStatus:
|
|
|
174
173
|
) as mock_get_client:
|
|
175
174
|
mock_client = mock_get_client.return_value
|
|
176
175
|
mock_client.get_shipment = AsyncMock(
|
|
177
|
-
return_value={
|
|
176
|
+
return_value={
|
|
177
|
+
"id": 999,
|
|
178
|
+
"status": "confirmed",
|
|
179
|
+
"tracking_number": "TRACK999",
|
|
180
|
+
},
|
|
178
181
|
)
|
|
179
182
|
mock_client.close = AsyncMock()
|
|
180
183
|
|
|
181
184
|
result = await provider.fetch_shipment_status()
|
|
182
185
|
|
|
183
|
-
assert result
|
|
186
|
+
assert result.get("status") == ShipmentStatus.LABEL_READY.value
|
|
187
|
+
assert result.get("tracking_number") == "TRACK999"
|
|
184
188
|
|
|
185
189
|
|
|
186
190
|
class TestLockerCancelShipment:
|
|
@@ -260,10 +264,10 @@ class TestLockerVerifyCallback:
|
|
|
260
264
|
|
|
261
265
|
|
|
262
266
|
class TestLockerHandleCallback:
|
|
263
|
-
async def
|
|
267
|
+
async def test_extracts_status_and_tracking_number(self) -> None:
|
|
264
268
|
shipment = _FakeShipment()
|
|
265
269
|
provider = InPostLockerProvider(shipment, config={})
|
|
266
|
-
await provider.handle_callback(
|
|
270
|
+
update = await provider.handle_callback(
|
|
267
271
|
data={
|
|
268
272
|
"payload": {
|
|
269
273
|
"shipment_id": 999,
|
|
@@ -273,8 +277,30 @@ class TestLockerHandleCallback:
|
|
|
273
277
|
},
|
|
274
278
|
headers={},
|
|
275
279
|
)
|
|
276
|
-
|
|
277
|
-
|
|
280
|
+
|
|
281
|
+
assert update == {
|
|
282
|
+
"status": ShipmentStatus.LABEL_READY.value,
|
|
283
|
+
"tracking_number": "TRACK999",
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async def test_unknown_callback_status_returns_tracking_number_only(
|
|
287
|
+
self,
|
|
288
|
+
) -> None:
|
|
289
|
+
shipment = _FakeShipment()
|
|
290
|
+
provider = InPostLockerProvider(shipment, config={})
|
|
291
|
+
|
|
292
|
+
update = await provider.handle_callback(
|
|
293
|
+
data={
|
|
294
|
+
"payload": {
|
|
295
|
+
"shipment_id": 999,
|
|
296
|
+
"status": "brand_new_status",
|
|
297
|
+
"tracking_number": "TRACK999",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
headers={},
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
assert update == {"tracking_number": "TRACK999"}
|
|
278
304
|
|
|
279
305
|
|
|
280
306
|
class TestLockerAddressConversion:
|
|
@@ -293,11 +319,11 @@ class TestLockerAddressConversion:
|
|
|
293
319
|
"country_code": "PL",
|
|
294
320
|
}
|
|
295
321
|
peer = provider._address_to_peer(addr)
|
|
296
|
-
assert peer
|
|
297
|
-
assert peer
|
|
298
|
-
assert peer
|
|
299
|
-
assert peer
|
|
300
|
-
assert peer
|
|
322
|
+
assert peer.get("first_name") == "Jan"
|
|
323
|
+
assert peer.get("last_name") == "Kowalski"
|
|
324
|
+
assert peer.get("phone") == "500100200"
|
|
325
|
+
assert peer.get("address", {}).get("street") == "Marszalkowska"
|
|
326
|
+
assert peer.get("address", {}).get("post_code") == "00-001"
|
|
301
327
|
|
|
302
328
|
def test_converts_legacy_name_to_first_last(self) -> None:
|
|
303
329
|
shipment = _FakeShipment()
|
|
@@ -312,6 +338,6 @@ class TestLockerAddressConversion:
|
|
|
312
338
|
"country_code": "PL",
|
|
313
339
|
}
|
|
314
340
|
peer = provider._address_to_peer(addr)
|
|
315
|
-
assert peer
|
|
316
|
-
assert peer
|
|
317
|
-
assert peer
|
|
341
|
+
assert peer.get("first_name") == "Jan"
|
|
342
|
+
assert peer.get("last_name") == "Kowalski"
|
|
343
|
+
assert peer.get("address", {}).get("street") == "Marszalkowska 1"
|
{python_sendparcel_inpost-0.1.1 → python_sendparcel_inpost-0.1.2}/tests/test_status_mapping.py
RENAMED
|
@@ -5,6 +5,7 @@ from sendparcel.enums import ShipmentStatus
|
|
|
5
5
|
|
|
6
6
|
from sendparcel_inpost.status_mapping import (
|
|
7
7
|
SHIPX_TO_SENDPARCEL_STATUS,
|
|
8
|
+
build_shipment_update,
|
|
8
9
|
map_shipx_status,
|
|
9
10
|
)
|
|
10
11
|
|
|
@@ -55,3 +56,33 @@ class TestStatusMapping:
|
|
|
55
56
|
for shipx, sendparcel in SHIPX_TO_SENDPARCEL_STATUS.items():
|
|
56
57
|
assert isinstance(shipx, str)
|
|
57
58
|
assert isinstance(sendparcel, ShipmentStatus)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestBuildShipmentUpdate:
|
|
62
|
+
def test_returns_normalized_status_and_tracking_number(self) -> None:
|
|
63
|
+
update = build_shipment_update(
|
|
64
|
+
"confirmed",
|
|
65
|
+
tracking_number="TRACK123",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
assert update == {
|
|
69
|
+
"status": ShipmentStatus.LABEL_READY.value,
|
|
70
|
+
"tracking_number": "TRACK123",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def test_returns_tracking_number_without_status_for_unknown_mapping(
|
|
74
|
+
self,
|
|
75
|
+
) -> None:
|
|
76
|
+
update = build_shipment_update(
|
|
77
|
+
"brand_new_shipx_status",
|
|
78
|
+
tracking_number="TRACK999",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert update == {"tracking_number": "TRACK999"}
|
|
82
|
+
|
|
83
|
+
def test_returns_empty_update_for_unknown_status_without_tracking_number(
|
|
84
|
+
self,
|
|
85
|
+
) -> None:
|
|
86
|
+
update = build_shipment_update("brand_new_shipx_status")
|
|
87
|
+
|
|
88
|
+
assert update == {}
|