Developer CLI for testing and debugging python-sendparcel providers.
Alpha (0.1.0) — API may change between minor releases. Pin your dependency if you use it in production.
- Four commands —
init,providers,check-config, andcreate-labelcover the full provider testing workflow. - Auto-generated config —
initdiscovers all installed providers and generates a TOML config file with required fields from each provider'sconfig_schema. - Provider discovery —
providerslists every installed provider with its slug, display name, confirmation method, supported countries, and configuration status. - Configuration validation —
check-configinspects a specific provider's config fields, showing required/type/value/status with secret masking. - End-to-end label creation —
create-labelcreates a test shipment and downloads the label through the fullShipmentFlowpipeline. - Flexible address input — sender and receiver addresses can be provided via JSON files, inline CLI flags, or a combination of both (flags override file values).
- Provider kwargs passthrough — arbitrary provider-specific parameters via
--kwarg KEY=VALUEflags. - Rich output — formatted tables via Rich for all command output.
- TOML configuration — config stored at
~/.config/sendparcel/config.toml(XDG convention), overridable via--configflag orSENDPARCEL_CONFIGenvironment variable. - Async-first — uses
anyioto run the asyncShipmentFlowfrom the synchronous CLI context.
uv add python-sendparcel-cliOr with pip:
pip install python-sendparcel-cliThis installs the sendparcel command. Providers are auto-discovered via the sendparcel.providers entry-point group — install any provider plugin (e.g. python-sendparcel-inpost, python-sendparcel-dpdpl) and it will appear automatically.
Generate a config file with empty sections for all installed providers:
sendparcel init
# Config created at /home/user/.config/sendparcel/config.tomlsendparcel providersOutput (example with InPost and DPD installed):
┌─────────────────────────────────────────────────────────────────────┐
│ Installed Providers │
├────────────────┬──────────────────┬────────┬───────────┬───────────┤
│ Slug │ Name │ Method │ Countries │ Config │
├────────────────┼──────────────────┼────────┼───────────┼───────────┤
│ inpost_locker │ InPost Paczkomat │ PUSH │ PL │ OK │
│ inpost_courier │ InPost Kurier │ PUSH │ PL │ --- │
│ dpd_standard │ DPD Kurier │ NONE │ PL │ OK │
└────────────────┴──────────────────┴────────┴───────────┴───────────┘
sendparcel check-config inpost_lockerOutput shows each config field with its required/type/value/status:
┌──────────────────────────────────────────────────────────────────┐
│ Config: inpost_locker │
├──────────────────┬──────────┬──────┬────────────────┬────────────┤
│ Field │ Required │ Type │ Value │ Status │
├──────────────────┼──────────┼──────┼────────────────┼────────────┤
│ token │ Yes │ str │ *** │ OK │
│ organization_id │ Yes │ int │ 12345 │ OK │
│ sandbox │ No │ bool │ (default: False)│ OK │
└──────────────────┴──────────┴──────┴────────────────┴────────────┘
Secret fields (like token) are masked in the output.
Using JSON address files:
sendparcel create-label inpost_locker \
--sender-file sender.json \
--receiver-file receiver.json \
--weight 2.5 \
--output-dir ./labels \
--kwarg target_point=KRA010Using inline flags:
sendparcel create-label dpd_standard \
--sender-first-name "Jan" \
--sender-last-name "Nadawca" \
--sender-phone "500100200" \
--sender-email "sender@test.com" \
--sender-street "Testowa" \
--sender-building-number "1" \
--sender-city "Warszawa" \
--sender-postal-code "00-001" \
--sender-country-code "PL" \
--receiver-first-name "Anna" \
--receiver-last-name "Odbiorca" \
--receiver-phone "600200300" \
--receiver-email "receiver@test.com" \
--weight 2.5 \
--output-dir ./labelsThe CLI reads configuration from a TOML file. The location is resolved in this order:
--config <path>flag (highest priority)SENDPARCEL_CONFIGenvironment variable~/.config/sendparcel/config.toml(XDG default)
[providers.inpost_locker]
token = "your-shipx-api-token"
organization_id = 12345
sandbox = true
[providers.inpost_courier]
token = "your-shipx-api-token"
organization_id = 12345
sandbox = true
[providers.dpd_standard]
login = "your-dpd-login"
password = "your-dpd-password"
master_fid = 1495
sandbox = trueEach [providers.<slug>] section contains the key-value pairs expected by that provider's config_schema. Run sendparcel init to auto-generate this file with the correct fields for all installed providers.
Discovers all installed providers via entry points and generates a config file with empty required fields and default values from each provider's config_schema.
| Option | Type | Default | Description |
|---|---|---|---|
--config |
Path |
~/.config/sendparcel/config.toml |
Config file path |
If the config file already exists, the command prints a notice and does not overwrite it.
Lists all installed providers in a Rich table with their slug, display name, confirmation method, supported countries, and configuration status (OK if all required fields are present, --- otherwise).
| Option | Type | Default | Description |
|---|---|---|---|
--config |
Path |
~/.config/sendparcel/config.toml |
Config file path |
Displays a detailed table for a specific provider's configuration. Each field shows whether it is required, its type, its current value (secrets are masked), and status (OK / MISSING / ---).
| Argument/Option | Type | Default | Description |
|---|---|---|---|
slug |
str |
(required) | Provider slug (e.g. inpost_locker) |
--config |
Path |
~/.config/sendparcel/config.toml |
Config file path |
Exits with code 1 if the provider slug is not found.
Creates a test shipment through the full ShipmentFlow pipeline (create shipment + create label) and saves the label file to disk.
| Argument/Option | Type | Default | Description |
|---|---|---|---|
slug |
str |
(required) | Provider slug |
--config |
Path |
~/.config/sendparcel/config.toml |
Config file path |
--sender-file |
Path |
None |
Sender address JSON file |
--receiver-file |
Path |
None |
Receiver address JSON file |
--sender-first-name |
str |
None |
Sender first name (inline) |
--sender-last-name |
str |
None |
Sender last name (inline) |
--sender-phone |
str |
None |
Sender phone (inline) |
--sender-email |
str |
None |
Sender email (inline) |
--sender-street |
str |
None |
Sender street (inline) |
--sender-building-number |
str |
None |
Sender building number (inline) |
--sender-city |
str |
None |
Sender city (inline) |
--sender-postal-code |
str |
None |
Sender postal code (inline) |
--sender-country-code |
str |
None |
Sender country code (inline) |
--sender-company |
str |
None |
Sender company name (inline) |
--receiver-first-name |
str |
None |
Receiver first name (inline) |
--receiver-last-name |
str |
None |
Receiver last name (inline) |
--receiver-phone |
str |
None |
Receiver phone (inline) |
--receiver-email |
str |
None |
Receiver email (inline) |
--receiver-street |
str |
None |
Receiver street (inline) |
--receiver-building-number |
str |
None |
Receiver building number (inline) |
--receiver-city |
str |
None |
Receiver city (inline) |
--receiver-postal-code |
str |
None |
Receiver postal code (inline) |
--receiver-country-code |
str |
None |
Receiver country code (inline) |
--receiver-company |
str |
None |
Receiver company name (inline) |
--weight |
float |
1.0 |
Parcel weight in kg |
--output-dir |
Path |
. (current dir) |
Directory to save the label file |
--kwarg |
str |
None |
Provider kwargs as KEY=VALUE (repeatable) |
Addresses can be provided three ways:
- JSON file only —
--sender-file sender.json - Inline flags only —
--sender-first-name "Jan" --sender-last-name "Nadawca" ... - Combined — load a base from file, override specific fields with inline flags
At least one of sender or receiver address must be provided. Exits with code 1 if neither is given or if the provider slug is not found.
{
"first_name": "Jan",
"last_name": "Nadawca",
"phone": "500100200",
"email": "sender@test.com",
"street": "Testowa",
"building_number": "1",
"city": "Warszawa",
"postal_code": "00-001",
"country_code": "PL"
}Optional fields: company, flat_number.
Use --kwarg KEY=VALUE to pass provider-specific parameters. This flag is repeatable:
sendparcel create-label inpost_locker \
--sender-file sender.json \
--receiver-file receiver.json \
--kwarg target_point=KRA010 \
--kwarg sending_method=dispatch_orderThese kwargs are forwarded directly to ShipmentFlow.create_shipment() and ShipmentFlow.create_label().
The create-label command saves labels to disk automatically:
- Base64 payloads from
label["content_base64"]are decoded and saved as binary.pdffiles. - URL results from
label["url"]are saved as text files containing the URL. - Binary labels are saved as
label-<external_id>.pdf; URL-only labels are saved aslabel-<external_id>.txt.
While primarily a CLI tool, the package exports its models and config manager for scripting:
from sendparcel_cli import CLIShipment, CLIShipmentRepository, ConfigManager
# Load config
mgr = ConfigManager() # uses default path
config = mgr.get_provider_config("inpost_locker")
# In-memory shipment + repository for scripting
shipment = CLIShipment(provider="inpost_locker")
repo = CLIShipmentRepository()The CLI handles errors with colored Rich output and appropriate exit codes:
| Situation | Behavior |
|---|---|
| Unknown provider slug | Prints Provider '<slug>' not found. in red, exits with code 1 |
| Missing sender/receiver addresses | Prints error message, exits with code 1 |
Invalid --kwarg format |
Raises BadParameter with format hint |
| Address file not found | Raises BadParameter with file path |
Config file already exists (init) |
Prints notice, does not overwrite |
| Label creation failure | Silently suppressed (shipment result is still displayed) |
| Any other provider error | Prints error in red, exits with code 1 |
| Dependency | Version |
|---|---|
| Python | >= 3.12 |
| python-sendparcel | >= 0.1.1 |
| typer | >= 0.15.0 |
| rich | >= 13.0 |
| tomli-w | >= 1.0 |
The test suite uses pytest with pytest-asyncio (asyncio_mode = "auto")
and Typer's CliRunner for command testing.
# Install dev dependencies
uv sync --extra dev
# Run the full test suite
uv run pytest
# With coverage
uv run pytest --cov=sendparcel_cli --cov-report=term-missingTest coverage includes:
test_config.py—ConfigManagerload/save/roundtrip, provider config get/set, flow config format, default path resolution.test_main.py— all four CLI commands tested viaCliRunner:init(create + no-overwrite),providers(listing),check-config(missing fields + unknown provider),create-label(JSON files, inline flags, kwarg passthrough, missing addresses).test_models.py—CLIOrder,CLIShipment,CLIShipmentRepositoryprotocol conformance, weight calculation, unique IDs, mutable fields, async CRUD operations.test_output.py— Rich table rendering for providers, shipment results, and config checks including secret masking.
- Author: Dominik Kozaczko (dominik@kozaczko.info)
- Built on top of python-sendparcel core library
- CLI powered by Typer and Rich