Skip to main content

python-sendparcel

Framework-agnostic parcel shipping core for Python.

Alpha notice: 0.3.0 is 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 Shipment and ShipmentRepository protocols so adapters can use their own models.

Core contract

  • ShipmentFlow.create_shipment(...) -> CreateShipmentOutcome
  • ShipmentFlow.create_label(...) -> CreateLabelOutcome
  • ShipmentFlow.handle_callback(...) -> ShipmentUpdateOutcome
  • ShipmentFlow.fetch_and_update_status(...) -> ShipmentUpdateOutcome
  • ShipmentFlow.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 update_fields(self, shipment_id: str, **fields) -> MyShipment:
        shipment = self._store[shipment_id]
        for key, value in fields.items():
            setattr(shipment, key, value)
        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

All providers subclass BaseProvider; capability methods raise ProviderCapabilityError unless overridden:

  • BaseProvider.create_shipment(...) returns ShipmentCreateResult (required).
  • BaseProvider.confirmation_method defaults to ConfirmationMethod.NONE.
  • BaseProvider.create_label(...) returns LabelInfo.
  • BaseProvider.verify_callback(ctx) / handle_callback(ctx) return None / ShipmentUpdateResult.
  • BaseProvider.fetch_shipment_status(...) returns ShipmentUpdateResult.
  • BaseProvider.cancel_shipment(...) returns bool.

Use ConfirmationMethod.PUSH only when the callback methods are overridden and ConfirmationMethod.PULL only when fetch_shipment_status is. See docs/provider-authoring.md for the full guide.

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

  • django
  • inpost
  • dpdpl
  • orlenpaczka
  • cli
  • frameworks
  • providers
  • all

Development

uv sync --extra dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run ty check
uv run mypy src

Metadata

Release files for python-sendparcel 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-sendparcel 0.3.2
File Size Uploaded
python_sendparcel-0.3.2.tar.gz 42.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for python-sendparcel 0.3.2
File Interpreter ABI Platform
python_sendparcel-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 67.6 kB

Release files / python_sendparcel-0.3.2.tar.gz

Download URL python_sendparcel-0.3.2.tar.gz
Size 42.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ccf1140e0b1840bb7465c0f279a5e3a6d9f834a3c99c68f2be3b65aac8efdab1
BLAKE2b-256 checksum
How to use checksums
5dbd5adf1faf0b76910dbc6300f9ec4a2c34e025b469eefe290069aba4ba34ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.

Transparency log

Release files / python_sendparcel-0.3.2-py3-none-any.whl

Download URL python_sendparcel-0.3.2-py3-none-any.whl
Size 25.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e87cb6aa58ae1343c0542610b61b9a6bd727f25007294e97bc868367448fe45e
BLAKE2b-256 checksum
How to use checksums
14a1f930c77ed43f5a56c75951d801a26e3dbd10eff831aeac2b1b5a7c6e7af2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.2 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page