Skip to main content

DPD Poland provider for python-sendparcel.

Project description

python-sendparcel-dpdpl

PyPI Python Version License

DPD Poland API provider for the python-sendparcel shipping ecosystem.

Alpha (0.1.1) — API may change between minor releases. Pin your dependency if you use it in production.

Features

  • Two providersDPDStandardProvider (door-to-door courier) and DPDPickupProvider (PUDO pickup point) as separate BaseProvider subclasses.
  • Honest capability metadata — both providers declare ConfirmationMethod.NONE because this package does not implement DPD status callbacks or polling.
  • Standalone DPD clientDPDClient async HTTP wrapper usable independently of the sendparcel framework.
  • Auto-discovery — both providers register via the sendparcel.providers entry-point group; no manual registration needed.
  • Multiple courier services — standard, express, next-day, and same-day delivery options.
  • Pickup point support — PUDO delivery with simplified receiver addressing (contact info only, no full address).
  • Address conversion — automatic conversion between sendparcel AddressInfo and DPD address format, with legacy name/line1 fallback.
  • Structured error handlingDPDAPIError hierarchy inheriting from core CommunicationError with status codes and validation details.
  • Async-first — fully asynchronous with httpx and anyio.

Installation

uv add python-sendparcel-dpdpl

Or with pip:

pip install python-sendparcel-dpdpl

Both providers are auto-discovered via the sendparcel.providers entry-point group — no manual registration needed.

Quick Start

Using providers through sendparcel

The providers integrate with the sendparcel flow automatically:

from sendparcel.registry import PluginRegistry

# Providers are discovered via entry points
registry = PluginRegistry()
choices = registry.get_choices()
# [('dpd_standard', 'DPD Kurier'), ('dpd_pickup', 'DPD Pickup'), ...]

Creating a standard courier shipment

provider = DPDStandardProvider(shipment=shipment, config={
    "login": "your-dpd-login",
    "password": "your-dpd-password",
    "master_fid": 1495,
    "sandbox": True,  # use demo environment for testing
})

result = await provider.create_shipment(
    reference="ORDER-123",          # optional: package reference
    cod_amount=150.00,              # optional: cash on delivery
    cod_currency="PLN",             # optional: default "PLN"
    declared_value=500.00,          # optional: declared value
)
# result["external_id"] = "12345678"  (session ID)
# result["tracking_number"] = "0000123456789"  (waybill number)

Creating a pickup point shipment

provider = DPDPickupProvider(shipment=shipment, config={
    "login": "your-dpd-login",
    "password": "your-dpd-password",
    "master_fid": 1495,
    "sandbox": True,
})

result = await provider.create_shipment(
    pickup_point="PL14509",  # required: DPD Pickup point ID
    reference="ORDER-456",   # optional
)
# result["external_id"] = "12345679"  (session ID)
# result["tracking_number"] = "0000123456790"  (waybill number)

Generating a label

After creating a shipment, generate the waybill label using the session ID stored in shipment.external_id:

label = await provider.create_label(
    label_format="A4",       # optional: "A4" or "LBL_PRINTER"
    doc_format="PDF",        # optional: "PDF", "ZPL", "EPL"
    label_type="BIC3",       # optional: "BIC3" or "EXTENDED"
)
# label["format"] = "PDF"
# label["content_base64"] = "JVBERi0xLjQ..."  (base64-encoded label)

# Decode the label to raw bytes
from sendparcel_dpdpl import DPDClient
label_bytes = DPDClient.decode_label(label["content_base64"])
with open("label.pdf", "wb") as f:
    f.write(label_bytes)

Using DPDClient standalone

The HTTP client can be used independently of the sendparcel framework:

from sendparcel_dpdpl import DPDClient

async with DPDClient(
    login="your-login",
    password="your-password",
    master_fid=1495,
    sandbox=True,
) as client:
    # Create packages and get waybill numbers
    result = await client.generate_packages_numbers(payload={
        "generationPolicy": "ALL_OR_NOTHING",
        "packages": [{
            "sender": {
                "name": "Nadawca Sp. z o.o.",
                "address": "Krakowska 10",
                "city": "Krakow",
                "postalCode": "30-001",
                "countryCode": "PL",
                "phone": "500100200",
            },
            "receiver": {
                "name": "Jan Kowalski",
                "address": "Warszawska 5/3",
                "city": "Warszawa",
                "postalCode": "00-001",
                "countryCode": "PL",
                "phone": "600200300",
                "email": "jan@example.com",
            },
            "payerFID": 1495,
            "parcels": [{"weight": 2.5, "sizeX": 30, "sizeY": 20, "sizeZ": 15}],
        }],
    })

    # Generate labels (advise parcels)
    session_id = result["sessionId"]
    labels = await client.generate_sped_labels(payload={
        "labelSearchParams": {
            "policy": "STOP_ON_FIRST_ERROR",
            "session": {"sessionId": session_id, "type": "DOMESTIC"},
        },
        "outputDocFormat": "PDF",
        "format": "A4",
        "outputType": "BIC3",
    })

    # Decode base64 label to bytes
    pdf_bytes = DPDClient.decode_label(labels["documentData"])

    # All-in-one: create + label + advise
    shipment = await client.generate_shipment(payload={...})

    # Generate handover protocol
    protocol = await client.generate_protocol(payload={...})

    # Validate postal code
    check = await client.check_postal_code("PL", "30-001")

    # Check courier availability
    avail = await client.get_courier_availability("PL", "30-001")

    # Order courier pickup
    pickup = await client.create_pickup_call(payload={...})

    # Cancel courier pickup
    await client.cancel_pickup_call(
        order_number="12345",
        check_sum="abc123",
    )

Configuration

Provider configuration is passed as a dict either through the config constructor parameter or via your framework adapter's settings:

Key Type Default Description
login str (required) DPD API login
password str (required) DPD API password
master_fid int (required) DPD master FID number (payer identifier)
sandbox bool False Use demo API endpoint
base_url str None Override API base URL (takes precedence over sandbox)
timeout float 30.0 HTTP request timeout in seconds

API endpoints

Environment Base URL
Production https://dpdservices.dpd.com.pl
Sandbox https://dpdservicesdemo.dpd.com.pl

Authentication

DPD uses HTTP Basic Auth (login/password) plus a master FID header (x-dpd-fid). All three credentials (login, password, master_fid) are required.

Integration with framework adapters

Pass DPD configuration through your adapter's provider settings:

# Django settings.py
SENDPARCEL_PROVIDER_SETTINGS = {
    "dpd_standard": {
        "login": "your-dpd-login",
        "password": "your-dpd-password",
        "master_fid": 1495,
        "sandbox": True,
    },
    "dpd_pickup": {
        "login": "your-dpd-login",
        "password": "your-dpd-password",
        "master_fid": 1495,
        "sandbox": True,
    },
}

# FastAPI / Litestar
config = SendparcelConfig(
    default_provider="dpd_standard",
    providers={
        "dpd_standard": {
            "login": "your-dpd-login",
            "password": "your-dpd-password",
            "master_fid": 1495,
            "sandbox": True,
        },
    },
)

Providers

DPDStandardProvider

Door-to-door courier delivery. Supports multiple service levels.

  • Slug: dpd_standard
  • Services: dpd_standard, dpd_express, dpd_next_day, dpd_today
  • Confirmation method: NONE (no push or pull status updates)
  • Supported countries: PL

create_shipment parameters:

Parameter Required Description
cod_amount no Cash on delivery amount
cod_currency no COD currency (default "PLN")
declared_value no Declared value amount
declared_currency no Declared value currency (default "PLN")
services no Additional transport services (list of dicts or service code strings)
reference no Package reference
generation_policy no Error handling policy (default ALL_OR_NOTHING)

DPDPickupProvider

PUDO (Pick Up Drop Off) pickup point delivery. The receiver picks up the parcel from a DPD Pickup point.

  • Slug: dpd_pickup
  • Service: dpd_pickup
  • Confirmation method: NONE (no push or pull status updates)
  • Supported countries: PL

create_shipment parameters:

Parameter Required Description
pickup_point yes DPD Pickup point ID (e.g. "PL14509")
cod_amount no Cash on delivery amount
cod_currency no COD currency (default "PLN")
services no Additional transport services
reference no Package reference
generation_policy no Error handling policy (default ALL_OR_NOTHING)

PUDO receivers only need contact information (name, phone, email, country code) — the pickup point provides the delivery address.

Common provider methods

Both providers implement a subset of the BaseProvider interface:

Method Purpose
create_shipment(**kwargs) Create a shipment in DPD (returns session ID + waybill)
create_label(**kwargs) Generate waybill label (base64-encoded, PDF/ZPL/EPL)

create_label parameters (both providers):

Parameter Required Description
label_format no Page format: "A4" or "LBL_PRINTER" (default "A4")
doc_format no Document format: "PDF", "ZPL", "EPL" (default "PDF")
label_type no Label type: "BIC3" or "EXTENDED" (default "BIC3")

Note: DPD providers in this package currently support shipment creation and label generation only. They do not implement fetch_shipment_status, cancel_shipment, verify_callback, or handle_callback, so their confirmation method is NONE.

Shipment flow

DPD uses a two-step flow:

  1. create_shipment() — calls generatePackagesNumbers to register packages and obtain a session ID + waybill numbers.
  2. create_label() — calls generateSpedLabels with the session ID to generate and advise the label. The label is returned as a base64-encoded string.

The session ID is stored in shipment.external_id and used automatically by create_label(). This package does not currently expose shipment status updates after creation.

Address Handling

The providers accept sendparcel.types.AddressInfo and convert it to the DPD address format. Two addressing styles are supported:

Structured (preferred):

address: AddressInfo = {
    "first_name": "Jan",
    "last_name": "Kowalski",
    "company": "Firma Sp. z o.o.",
    "street": "Krakowska",
    "building_number": "10",
    "flat_number": "5",
    "city": "Krakow",
    "postal_code": "30-001",
    "country_code": "PL",
    "phone": "500100200",
    "email": "jan@example.com",
}
# Converted to: {"name": "Jan Kowalski", "address": "Krakowska 10/5", ...}

Legacy style (fallback):

address: AddressInfo = {
    "name": "Jan Kowalski",
    "line1": "Krakowska 10/5",
    "city": "Krakow",
    "postal_code": "30-001",
    "phone": "500100200",
}

PUDO receiver addressing: For DPDPickupProvider, receiver addresses are simplified — only contact information (name, phone, email, country_code) is sent. The pickup point provides the physical delivery address.

Error Handling

All DPD API errors inherit from sendparcel.exceptions.CommunicationError:

from sendparcel_dpdpl.exceptions import (
    DPDAPIError,              # base: any non-2xx response
    DPDAuthenticationError,   # 401 Unauthorized
    DPDValidationError,       # 400 Bad Request
)

try:
    result = await client.generate_packages_numbers(payload=payload)
except DPDAuthenticationError:
    # Invalid login/password or master FID
    ...
except DPDValidationError as exc:
    # Payload validation failed
    print(exc.detail)   # human-readable message
    print(exc.errors)   # list of error dicts from DPD API
except DPDAPIError as exc:
    # Other API error
    print(exc.status_code, exc.detail)

DPDClient API Reference

The standalone DPDClient provides direct access to DPD Poland API endpoints:

Method HTTP Endpoint Description
generate_packages_numbers(payload) POST /public/shipment/v1/generatePackagesNumbers Create packages, get waybill numbers
generate_sped_labels(payload) POST /public/shipment/v1/generateSpedLabels Generate waybill labels (advise parcels)
generate_shipment(payload) POST /public/shipment/v1/generateShipment All-in-one: create + label + advise
generate_protocol(payload) POST /public/shipment/v1/generateProtocol Generate handover protocol
check_postal_code(country_code, zip_code) GET /public/routing/v1/postalCode Validate postal code for delivery
get_courier_availability(country_code, zip_code) GET /public/courierorder/v1/courierOrderAvailability Check courier availability
create_pickup_call(payload) POST /public/courierorder/v1/packagesPickupCall Order courier pickup
cancel_pickup_call(order_number, check_sum) DELETE /public/courierorder/v1/packagesPickupCall Cancel courier pickup
decode_label(document_data) (static) Decode base64 label to bytes

Supported Versions

Dependency Version
Python >= 3.12
python-sendparcel >= 0.1.1
httpx >= 0.27.0
anyio >= 4.0

Running Tests

The test suite uses pytest with pytest-asyncio (asyncio_mode = "auto") and respx for HTTP mocking.

# Install dev dependencies
uv sync --extra dev

# Run the full test suite
uv run pytest

# With coverage
uv run pytest --cov=sendparcel_dpdpl --cov-report=term-missing

Credits

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

python_sendparcel_dpdpl-0.1.1.tar.gz (19.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

python_sendparcel_dpdpl-0.1.1-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file python_sendparcel_dpdpl-0.1.1.tar.gz.

File metadata

  • Download URL: python_sendparcel_dpdpl-0.1.1.tar.gz
  • Upload date:
  • Size: 19.0 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

Hashes for python_sendparcel_dpdpl-0.1.1.tar.gz
Algorithm Hash digest
SHA256 936bb704e0389a9a7d05f08f961a61bce0d5d93d71baf3a6ec92f4f106567f70
MD5 cfc8363cdf4f97d02aa58a9163512e07
BLAKE2b-256 6c51eea6cc02638ab6aa624adb7ff01cee6eadee77d35c96d245149960125f18

See more details on using hashes here.

File details

Details for the file python_sendparcel_dpdpl-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: python_sendparcel_dpdpl-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 17.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

Hashes for python_sendparcel_dpdpl-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 20ab1a5f324fe251e9e86bb991d51a72cf4084e23e0c0581040043b9a4ad9035
MD5 94b9b058e5d6b79e1a93347881466744
BLAKE2b-256 2379e3564205c9c0bbba9ff9fb4cd9aeb1056196264a235d9cb2cfef01c9d79a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page