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