python-sendparcel 0.3.1__tar.gz → 0.3.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-0.3.1 → python_sendparcel-0.3.2}/CHANGELOG.md +9 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/PKG-INFO +1 -1
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/pyproject.toml +1 -1
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/flow.py +17 -11
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/providers/dummy.py +21 -2
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_builtin_dummy_provider.py +31 -7
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.github/workflows/ci.yml +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.github/workflows/ecosystem-smoke.yml +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.github/workflows/release.yml +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.gitignore +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.pre-commit-config.yaml +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/CONTRIBUTING.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/LICENSE +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/README.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/Makefile +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/compatibility-matrix.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/conf.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/getting-started.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/index.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/installation-matrix.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/plans/2026-02-17-order-optional.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/provider-authoring.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/release-policy.md +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/requirements.txt +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/__init__.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/batch.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/concurrent.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/enums.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/exceptions.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/factory.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/fsm.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/logging.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/protocols.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/provider.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/providers/__init__.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/py.typed +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/registry.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/types.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/conftest.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_batch.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_cancel_outcome.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_concurrent.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_enums.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_exceptions.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_flow.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_fsm.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_integration.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_protocols.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_provider.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_provider_enhancements.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_public_api.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_quote.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_registry.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_search_points.py +0 -0
- {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_types.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.2 (2026-07-09)
|
|
4
|
+
|
|
5
|
+
- `DummyProvider.cancel_shipment` returns a structured `CancelOutcome`
|
|
6
|
+
(configurable via `cancel_success` / `cancel_reason` settings) instead of a
|
|
7
|
+
bare bool — aligning core's own provider with the 0.3.1 cancel trait that
|
|
8
|
+
`ShipmentFlow.cancel_shipment` already requires.
|
|
9
|
+
- `ShipmentFlow`: typed returns for `cancel_shipment` / `search_points`
|
|
10
|
+
(mypy no-any-return).
|
|
11
|
+
|
|
3
12
|
All notable changes to this project will be documented in this file.
|
|
4
13
|
|
|
5
14
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-sendparcel
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Framework-agnostic parcel shipping core for Python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/python-sendparcel/python-sendparcel
|
|
6
6
|
Project-URL: Repository, https://github.com/python-sendparcel/python-sendparcel
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, cast
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
|
|
@@ -227,7 +227,10 @@ class ShipmentFlow:
|
|
|
227
227
|
- ``AUTH_ERROR`` → re-raises provider auth error.
|
|
228
228
|
"""
|
|
229
229
|
provider = self._get_provider(shipment)
|
|
230
|
-
outcome =
|
|
230
|
+
outcome = cast(
|
|
231
|
+
"CancelOutcome",
|
|
232
|
+
await self._call_provider(provider.cancel_shipment(**kwargs)),
|
|
233
|
+
)
|
|
231
234
|
if outcome.get("reason") == CancelReason.TRANSIENT_ERROR:
|
|
232
235
|
raise CommunicationError(
|
|
233
236
|
outcome.get("detail") or "Cancel failed with transient error",
|
|
@@ -281,15 +284,18 @@ class ShipmentFlow:
|
|
|
281
284
|
provider_class,
|
|
282
285
|
provider_config,
|
|
283
286
|
)
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
287
|
+
return cast(
|
|
288
|
+
"list[PickupPoint]",
|
|
289
|
+
await self._call_provider(
|
|
290
|
+
provider.search_points(
|
|
291
|
+
query=query,
|
|
292
|
+
near=near,
|
|
293
|
+
radius_m=radius_m,
|
|
294
|
+
point_type=point_type,
|
|
295
|
+
limit=limit,
|
|
296
|
+
**kwargs,
|
|
297
|
+
)
|
|
298
|
+
),
|
|
293
299
|
)
|
|
294
300
|
|
|
295
301
|
def _get_provider(self, shipment: Shipment) -> BaseProvider:
|
|
@@ -12,6 +12,8 @@ from sendparcel.provider import BaseProvider
|
|
|
12
12
|
from sendparcel.types import (
|
|
13
13
|
AddressInfo,
|
|
14
14
|
CallbackContext,
|
|
15
|
+
CancelOutcome,
|
|
16
|
+
CancelReason,
|
|
15
17
|
LabelInfo,
|
|
16
18
|
ParcelInfo,
|
|
17
19
|
ShipmentCreateResult,
|
|
@@ -78,6 +80,23 @@ class DummyProvider(BaseProvider):
|
|
|
78
80
|
status=self.get_setting("status_override", self.shipment.status)
|
|
79
81
|
)
|
|
80
82
|
|
|
81
|
-
async def cancel_shipment(self, **kwargs: Any) ->
|
|
83
|
+
async def cancel_shipment(self, **kwargs: Any) -> CancelOutcome:
|
|
84
|
+
"""Cancel with a configurable structured outcome.
|
|
85
|
+
|
|
86
|
+
Settings: ``cancel_success`` (bool, default True) and
|
|
87
|
+
``cancel_reason`` (a :class:`CancelReason` value used when the
|
|
88
|
+
cancel is configured to fail; defaults to ``not_cancellable``).
|
|
89
|
+
"""
|
|
82
90
|
await self._simulate_latency()
|
|
83
|
-
|
|
91
|
+
if bool(self.get_setting("cancel_success", True)):
|
|
92
|
+
return CancelOutcome(
|
|
93
|
+
cancelled=True, reason=CancelReason.CANCELLED, retryable=False
|
|
94
|
+
)
|
|
95
|
+
reason = CancelReason(
|
|
96
|
+
self.get_setting("cancel_reason", CancelReason.NOT_CANCELLABLE)
|
|
97
|
+
)
|
|
98
|
+
return CancelOutcome(
|
|
99
|
+
cancelled=False,
|
|
100
|
+
reason=reason,
|
|
101
|
+
retryable=reason == CancelReason.TRANSIENT_ERROR,
|
|
102
|
+
)
|
|
@@ -8,7 +8,12 @@ import pytest
|
|
|
8
8
|
from sendparcel.enums import ConfirmationMethod
|
|
9
9
|
from sendparcel.exceptions import InvalidCallbackError
|
|
10
10
|
from sendparcel.providers.dummy import DummyProvider
|
|
11
|
-
from sendparcel.types import
|
|
11
|
+
from sendparcel.types import (
|
|
12
|
+
AddressInfo,
|
|
13
|
+
CallbackContext,
|
|
14
|
+
CancelReason,
|
|
15
|
+
ParcelInfo,
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
_SENDER = AddressInfo(
|
|
14
19
|
name="Test Sender",
|
|
@@ -166,19 +171,38 @@ class TestFetchShipmentStatus:
|
|
|
166
171
|
|
|
167
172
|
class TestCancelShipment:
|
|
168
173
|
@pytest.mark.asyncio
|
|
169
|
-
async def
|
|
174
|
+
async def test_cancel_returns_outcome_by_default(self) -> None:
|
|
170
175
|
provider = DummyProvider(DummyShipment(), config={})
|
|
171
176
|
|
|
172
|
-
|
|
177
|
+
outcome = await provider.cancel_shipment()
|
|
173
178
|
|
|
174
|
-
assert
|
|
179
|
+
assert outcome["cancelled"] is True
|
|
180
|
+
assert outcome["reason"] == CancelReason.CANCELLED
|
|
181
|
+
assert outcome["retryable"] is False
|
|
175
182
|
|
|
176
183
|
@pytest.mark.asyncio
|
|
177
|
-
async def
|
|
184
|
+
async def test_cancel_can_be_configured_to_refuse(self) -> None:
|
|
178
185
|
provider = DummyProvider(
|
|
179
186
|
DummyShipment(), config={"cancel_success": False}
|
|
180
187
|
)
|
|
181
188
|
|
|
182
|
-
|
|
189
|
+
outcome = await provider.cancel_shipment()
|
|
190
|
+
|
|
191
|
+
assert outcome["cancelled"] is False
|
|
192
|
+
assert outcome["reason"] == CancelReason.NOT_CANCELLABLE
|
|
193
|
+
assert outcome["retryable"] is False
|
|
194
|
+
|
|
195
|
+
@pytest.mark.asyncio
|
|
196
|
+
async def test_cancel_reason_is_configurable(self) -> None:
|
|
197
|
+
provider = DummyProvider(
|
|
198
|
+
DummyShipment(),
|
|
199
|
+
config={
|
|
200
|
+
"cancel_success": False,
|
|
201
|
+
"cancel_reason": "refused_in_transit",
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
outcome = await provider.cancel_shipment()
|
|
183
206
|
|
|
184
|
-
assert
|
|
207
|
+
assert outcome["cancelled"] is False
|
|
208
|
+
assert outcome["reason"] == CancelReason.REFUSED_IN_TRANSIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|