Framework-agnostic parcel shipping core for Python.
Project description
python-sendparcel
Framework-agnostic parcel shipping core for Python.
Alpha notice:
0.1.1is still unstable. The API can change fast because the ecosystem is still being cleaned up.
What it is
- Provider-agnostic shipment orchestration with a single core flow.
- Explicit shipment metadata persistence:
id,status,provider,external_id,tracking_number. - Label payloads are operation results, not persisted shipment fields.
- One normalized provider update contract for both callbacks and polling.
- Runtime-checkable
ShipmentandShipmentRepositoryprotocols so adapters can use their own models.
Core contract
ShipmentFlow.create_shipment(...) -> CreateShipmentOutcomeShipmentFlow.create_label(...) -> CreateLabelOutcomeShipmentFlow.handle_callback(...) -> ShipmentUpdateOutcomeShipmentFlow.fetch_and_update_status(...) -> ShipmentUpdateOutcomeShipmentFlow.cancel_shipment(...) -> bool
CreateShipmentOutcome and CreateLabelOutcome return label payloads when available.
The shipment object never stores label bytes or a persisted label_url in the core contract.
Quick start
from dataclasses import dataclass
from decimal import Decimal
import anyio
from sendparcel import ShipmentFlow
from sendparcel.types import AddressInfo, ParcelInfo
@dataclass
class MyShipment:
id: str
status: str = "new"
provider: str = ""
external_id: str = ""
tracking_number: str = ""
class InMemoryRepository:
def __init__(self) -> None:
self._store: dict[str, MyShipment] = {}
self._counter = 0
async def get_by_id(self, shipment_id: str) -> MyShipment:
return self._store[shipment_id]
async def create(self, **kwargs) -> MyShipment:
self._counter += 1
shipment = MyShipment(
id=str(self._counter),
status=str(kwargs.get("status", "new")),
provider=str(kwargs.get("provider", "")),
)
self._store[shipment.id] = shipment
return shipment
async def save(self, shipment: MyShipment) -> MyShipment:
self._store[shipment.id] = shipment
return shipment
async def main() -> None:
flow = ShipmentFlow(repository=InMemoryRepository())
created = await flow.create_shipment(
"dummy",
sender_address=AddressInfo(
name="Sender Co.",
line1="Marszalkowska 1",
city="Warsaw",
postal_code="00-001",
country_code="PL",
),
receiver_address=AddressInfo(
name="Jan Kowalski",
line1="Dluga 10",
city="Gdansk",
postal_code="80-001",
country_code="PL",
),
parcels=[ParcelInfo(weight_kg=Decimal("2.5"))],
)
print(created.shipment.status)
print(created.shipment.external_id)
print(created.shipment.tracking_number)
labelled = await flow.create_label(created.shipment)
print(labelled.label.get("url"))
anyio.run(main)
Provider model
BaseProvider.create_shipment(...)returnsShipmentCreateResult.BaseProvider.confirmation_methoddefaults toConfirmationMethod.NONE.LabelProvider.create_label(...)returnsLabelInfo.PushCallbackProvider.handle_callback(...)returnsShipmentUpdateResult.PullStatusProvider.fetch_shipment_status(...)returnsShipmentUpdateResult.CancellableProvider.cancel_shipment(...)returnsbool.
Use ConfirmationMethod.PUSH only with PushCallbackProvider and
ConfirmationMethod.PULL only with PullStatusProvider.
The core owns shipment state transitions. Providers translate carrier responses into normalized results.
Installation
pip install python-sendparcel
With uv:
uv add python-sendparcel
Extras
djangofastapilitestarinpostdpdplcliframeworksprovidersall
Development
uv sync --extra dev
uv run pytest
uv run ruff check src tests
uv run mypy src tests
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_sendparcel-0.1.1.tar.gz.
File metadata
- Download URL: python_sendparcel-0.1.1.tar.gz
- Upload date:
- Size: 23.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab41fdafea9d08a045151ffe741d046967cbd84fcadd87c97a07c790991d583e
|
|
| MD5 |
fe2c9bfed9725b3a91cb7c5d574abca8
|
|
| BLAKE2b-256 |
03ea14f94199adcb119f5b1755762d19af9d54c91ecb976aa5b66f751bfc35c2
|
File details
Details for the file python_sendparcel-0.1.1-py3-none-any.whl.
File metadata
- Download URL: python_sendparcel-0.1.1-py3-none-any.whl
- Upload date:
- Size: 13.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Manjaro Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4cb61defec9a4e082af2d9b6c2b7be5c19b0627271bb5cef56aea39a8c43fcd
|
|
| MD5 |
9d5a67333f5decd43ad1c9008d25a191
|
|
| BLAKE2b-256 |
3c97314831b0e59a97afc6111407944c702ad2032e3b8e5a6c977e250fba11fc
|