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.
Files changed (55) hide show
  1. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/CHANGELOG.md +9 -0
  2. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/PKG-INFO +1 -1
  3. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/pyproject.toml +1 -1
  4. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/flow.py +17 -11
  5. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/providers/dummy.py +21 -2
  6. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_builtin_dummy_provider.py +31 -7
  7. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.github/workflows/ci.yml +0 -0
  8. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.github/workflows/ecosystem-smoke.yml +0 -0
  9. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.github/workflows/release.yml +0 -0
  10. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.gitignore +0 -0
  11. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/.pre-commit-config.yaml +0 -0
  12. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/CONTRIBUTING.md +0 -0
  13. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/LICENSE +0 -0
  14. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/README.md +0 -0
  15. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/Makefile +0 -0
  16. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/compatibility-matrix.md +0 -0
  17. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/conf.py +0 -0
  18. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/getting-started.md +0 -0
  19. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/index.md +0 -0
  20. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/installation-matrix.md +0 -0
  21. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/plans/2026-02-17-order-optional.md +0 -0
  22. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/provider-authoring.md +0 -0
  23. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/release-policy.md +0 -0
  24. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/docs/requirements.txt +0 -0
  25. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/__init__.py +0 -0
  26. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/batch.py +0 -0
  27. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/concurrent.py +0 -0
  28. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/enums.py +0 -0
  29. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/exceptions.py +0 -0
  30. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/factory.py +0 -0
  31. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/fsm.py +0 -0
  32. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/logging.py +0 -0
  33. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/protocols.py +0 -0
  34. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/provider.py +0 -0
  35. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/providers/__init__.py +0 -0
  36. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/py.typed +0 -0
  37. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/registry.py +0 -0
  38. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/src/sendparcel/types.py +0 -0
  39. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/conftest.py +0 -0
  40. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_batch.py +0 -0
  41. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_cancel_outcome.py +0 -0
  42. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_concurrent.py +0 -0
  43. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_enums.py +0 -0
  44. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_exceptions.py +0 -0
  45. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_flow.py +0 -0
  46. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_fsm.py +0 -0
  47. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_integration.py +0 -0
  48. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_protocols.py +0 -0
  49. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_provider.py +0 -0
  50. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_provider_enhancements.py +0 -0
  51. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_public_api.py +0 -0
  52. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_quote.py +0 -0
  53. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_registry.py +0 -0
  54. {python_sendparcel-0.3.1 → python_sendparcel-0.3.2}/tests/test_search_points.py +0 -0
  55. {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.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-sendparcel"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "Framework-agnostic parcel shipping core for Python."
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -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 = await self._call_provider(provider.cancel_shipment(**kwargs))
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 await self._call_provider(
285
- provider.search_points(
286
- query=query,
287
- near=near,
288
- radius_m=radius_m,
289
- point_type=point_type,
290
- limit=limit,
291
- **kwargs,
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) -> bool:
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
- return bool(self.get_setting("cancel_success", True))
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 AddressInfo, CallbackContext, ParcelInfo
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 test_cancel_returns_true_by_default(self) -> None:
174
+ async def test_cancel_returns_outcome_by_default(self) -> None:
170
175
  provider = DummyProvider(DummyShipment(), config={})
171
176
 
172
- result = await provider.cancel_shipment()
177
+ outcome = await provider.cancel_shipment()
173
178
 
174
- assert result is True
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 test_cancel_can_be_configured_to_fail(self) -> None:
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
- result = await provider.cancel_shipment()
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 result is False
207
+ assert outcome["cancelled"] is False
208
+ assert outcome["reason"] == CancelReason.REFUSED_IN_TRANSIT