DPD Poland provider for python-sendparcel.
Project description
python-sendparcel-dpdpl
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 providers —
DPDStandardProvider(door-to-door courier) andDPDPickupProvider(PUDO pickup point) as separateBaseProvidersubclasses. - Honest capability metadata — both providers declare
ConfirmationMethod.NONEbecause this package does not implement DPD status callbacks or polling. - Standalone DPD client —
DPDClientasync HTTP wrapper usable independently of the sendparcel framework. - Auto-discovery — both providers register via the
sendparcel.providersentry-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
AddressInfoand DPD address format, with legacyname/line1fallback. - Structured error handling —
DPDAPIErrorhierarchy inheriting from coreCommunicationErrorwith status codes and validation details. - Async-first — fully asynchronous with
httpxandanyio.
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, orhandle_callback, so their confirmation method isNONE.
Shipment flow
DPD uses a two-step flow:
create_shipment()— callsgeneratePackagesNumbersto register packages and obtain a session ID + waybill numbers.create_label()— callsgenerateSpedLabelswith 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
- Author: Dominik Kozaczko (dominik@kozaczko.info)
- Built on top of python-sendparcel core library
- Integrates with the DPD Poland API
License
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_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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
936bb704e0389a9a7d05f08f961a61bce0d5d93d71baf3a6ec92f4f106567f70
|
|
| MD5 |
cfc8363cdf4f97d02aa58a9163512e07
|
|
| BLAKE2b-256 |
6c51eea6cc02638ab6aa624adb7ff01cee6eadee77d35c96d245149960125f18
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20ab1a5f324fe251e9e86bb991d51a72cf4084e23e0c0581040043b9a4ad9035
|
|
| MD5 |
94b9b058e5d6b79e1a93347881466744
|
|
| BLAKE2b-256 |
2379e3564205c9c0bbba9ff9fb4cd9aeb1056196264a235d9cb2cfef01c9d79a
|