Skip to main content

FastAPI adapter for python-sendparcel

Project description

fastapi-sendparcel

PyPI Python Version License Documentation

FastAPI adapter for the python-sendparcel shipping ecosystem.

Alpha notice — This package is at version 0.1.1 and its API is not yet stable. Breaking changes may occur in minor releases until 1.0.


Features

  • Router factory — single call to create_shipping_router() gives you a fully-configured APIRouter with shipment, label, status and callback endpoints.
  • Provider-agnostic — plug in any shipping provider that implements the python-sendparcel provider protocol.
  • Plugin registryFastAPIPluginRegistry discovers and manages provider plugins with optional per-provider routers.
  • Pydantic-native configurationSendparcelConfig reads from environment variables with the SENDPARCEL_ prefix.
  • Webhook callback handling — built-in endpoint for provider status callbacks with automatic retry queue support.
  • SQLAlchemy contrib — optional [sqlalchemy] extra provides SQLAlchemyShipmentRepository, SQLAlchemyRetryStore, and ready-made database models.
  • Exception mapping — core sendparcel exceptions are automatically converted to appropriate HTTP status codes (400, 404, 409, 502).
  • Async-first — fully asynchronous with async/await throughout.

Installation

Install the base package:

pip install fastapi-sendparcel

If you want SQLAlchemy-backed persistence (recommended):

pip install fastapi-sendparcel[sqlalchemy]

Note: The project uses uv for development. If you are contributing, run uv sync instead.

Quick Start

Below is a minimal but complete FastAPI application that wires up the shipping router with SQLAlchemy persistence.

from contextlib import asynccontextmanager
from collections.abc import AsyncGenerator

from fastapi import FastAPI
from sqlalchemy.ext.asyncio import (
    AsyncSession,
    async_sessionmaker,
    create_async_engine,
)

from fastapi_sendparcel import (
    SendparcelConfig,
    FastAPIPluginRegistry,
    create_shipping_router,
)
from fastapi_sendparcel.contrib.sqlalchemy.models import Base
from fastapi_sendparcel.contrib.sqlalchemy.repository import (
    SQLAlchemyShipmentRepository,
)
from fastapi_sendparcel.contrib.sqlalchemy.retry_store import (
    SQLAlchemyRetryStore,
)

# --- Database ---
engine = create_async_engine("sqlite+aiosqlite:///./shipments.db")
async_session = async_sessionmaker(engine, class_=AsyncSession)

# --- Sendparcel setup ---
config = SendparcelConfig(
    default_provider="my-provider",
    providers={
        "my-provider": {
            "api_key": "...",
        },
    },
)

repository = SQLAlchemyShipmentRepository(async_session)
retry_store = SQLAlchemyRetryStore(async_session)
registry = FastAPIPluginRegistry()

shipping_router = create_shipping_router(
    config=config,
    repository=repository,
    registry=registry,
    retry_store=retry_store,
)

# --- App ---
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    await engine.dispose()


app = FastAPI(title="My Shipping App", lifespan=lifespan)
app.include_router(shipping_router, prefix="/api/shipping")

The create_shipping_router function accepts all its arguments as keyword-only parameters:

Parameter Type Required Description
config SendparcelConfig Yes Adapter configuration instance
repository ShipmentRepository Yes Persistence backend for shipments
registry FastAPIPluginRegistry No Plugin registry (auto-created if omitted)
retry_store CallbackRetryStore No Storage for webhook retry queue

Configuration

SendparcelConfig extends Pydantic's BaseSettings and reads environment variables with the SENDPARCEL_ prefix.

Setting Env variable Type Default Description
default_provider SENDPARCEL_DEFAULT_PROVIDER str (required) Slug of the default shipping provider
providers SENDPARCEL_PROVIDERS dict[str, dict] {} Per-provider configuration dicts
retry_max_attempts SENDPARCEL_RETRY_MAX_ATTEMPTS int 5 Max retry attempts for failed callbacks
retry_backoff_seconds SENDPARCEL_RETRY_BACKOFF_SECONDS int 60 Base backoff interval between retries
retry_enabled SENDPARCEL_RETRY_ENABLED bool True Enable/disable callback retry queue

You can instantiate the config directly or let it read from the environment:

# Explicit values
config = SendparcelConfig(
    default_provider="inpost",
    providers={"inpost": {"api_key": "secret"}},
)

# From environment variables
# (set SENDPARCEL_DEFAULT_PROVIDER=inpost, etc.)
config = SendparcelConfig()

API Endpoints

The router created by create_shipping_router() exposes the following endpoints. All paths are relative to the prefix you mount the router at (e.g. /api/shipping).

Method Path Description
GET /shipments/health Healthcheck — returns {"status": "ok"}
POST /shipments Create a new shipment
POST /shipments/{shipment_id}/label Generate a shipping label
GET /shipments/{shipment_id}/status Fetch and update shipment status from the provider
POST /callbacks/{provider_slug}/{shipment_id} Handle a provider webhook callback

Request and Response Schemas

POST /shipments — request body:

{
  "reference_id": "my-ref-123",
  "provider": "my-provider",
  "sender_address": {
    "name": "John Smith",
    "line1": "1 Example St",
    "city": "Warsaw",
    "postal_code": "00-001",
    "country_code": "PL"
  },
  "receiver_address": {
    "name": "Jane Doe",
    "line1": "5 Destination St",
    "city": "Krakow",
    "postal_code": "30-001",
    "country_code": "PL"
  },
  "parcels": [
    {"weight_kg": 2.5}
  ]
}

The provider field is optional; when omitted, default_provider from the config is used. The reference_id field is optional and can be used for external reference tracking.

ShipmentOperationResponse — returned by shipment, label and status endpoints:

{
  "id": "abc-def",
  "status": "label_ready",
  "provider": "my-provider",
  "external_id": "EXT123",
  "tracking_number": "TRACK456",
  "label": {
    "format": "PDF",
    "url": "https://...",
    "content_base64": null
  },
  "update": null
}

Label payloads are returned as operation results. They are not persisted on the shipment model.

CallbackResponse — returned by the callback endpoint:

{
  "provider": "my-provider",
  "status": "accepted",
  "shipment": {
    "id": "abc-def",
    "status": "in_transit",
    "provider": "my-provider",
    "external_id": "EXT123",
    "tracking_number": "TRACK456"
  },
  "update": {
    "status": "in_transit",
    "tracking_number": "TRACK456",
    "tracking_events": []
  }
}

Exception Handling

The router automatically registers exception handlers that map core sendparcel exceptions to HTTP responses:

Exception HTTP Status Code
ShipmentNotFoundError 404 shipment_not_found
ProviderNotFoundError 404 provider_not_found
ProviderCapabilityError 409 provider_capability_error
InvalidCallbackError 400 invalid_callback
InvalidTransitionError 409 invalid_transition
CommunicationError 502 communication_error
SendParcelException 400 shipment_error

Protocols

CallbackRetryStore

Stores failed webhook callbacks for retry processing. The SQLAlchemy contrib provides a ready-made implementation (SQLAlchemyRetryStore), but you can implement this protocol with any backend (Redis, DynamoDB, etc.).

from fastapi_sendparcel import CallbackRetryStore


class MyRetryStore:
    async def store_failed_callback(
        self, shipment_id: str, payload: dict, headers: dict
    ) -> str: ...

    async def get_due_retries(self, limit: int = 10) -> list[dict]: ...

    async def mark_succeeded(self, retry_id: str) -> None: ...

    async def mark_failed(self, retry_id: str, error: str) -> None: ...

    async def mark_exhausted(self, retry_id: str) -> None: ...

SQLAlchemy Contrib

The optional [sqlalchemy] extra provides production-ready persistence components:

  • ShipmentModel — SQLAlchemy model mapped to the sendparcel_shipments table.
  • CallbackRetryModel — SQLAlchemy model mapped to the sendparcel_callback_retries table.
  • SQLAlchemyShipmentRepository — async repository implementing the ShipmentRepository protocol.
  • SQLAlchemyRetryStore — async retry store implementing the CallbackRetryStore protocol.

Both require an async_sessionmaker[AsyncSession] at construction time:

from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from fastapi_sendparcel.contrib.sqlalchemy.repository import (
    SQLAlchemyShipmentRepository,
)
from fastapi_sendparcel.contrib.sqlalchemy.retry_store import (
    SQLAlchemyRetryStore,
)

session_factory = async_sessionmaker(engine, class_=AsyncSession)

repository = SQLAlchemyShipmentRepository(session_factory)
retry_store = SQLAlchemyRetryStore(session_factory, backoff_seconds=60)

Example Project

The example/ directory contains a full demo application with:

  • Tabler-based UI with Jinja2 templates and HTMX
  • Shipment creation with sender/receiver address forms
  • Label generation and PDF download
  • A simulated delivery provider (delivery_sim.py)

Running the example

cd example
uv sync
uv run uvicorn app:app --reload

Then open http://localhost:8000 in your browser.

Supported Versions

Dependency Version
Python >= 3.12
FastAPI >= 0.115.0
Pydantic Settings >= 2.0.0
python-sendparcel >= 0.1.1
SQLAlchemy (optional) >= 2.0.0

Running Tests

uv sync --all-extras
uv run pytest

The test suite uses pytest with pytest-asyncio in auto mode. Configuration is in pyproject.toml.

Credits

Created and maintained by Dominik Kozaczko.

This project is the FastAPI adapter for the python-sendparcel ecosystem.

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

fastapi_sendparcel-0.1.1.tar.gz (39.1 kB view details)

Uploaded Source

Built Distribution

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

fastapi_sendparcel-0.1.1-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fastapi_sendparcel-0.1.1.tar.gz
  • Upload date:
  • Size: 39.1 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 fastapi_sendparcel-0.1.1.tar.gz
Algorithm Hash digest
SHA256 bbfa5bcdf247cebfd24ad4829bfa2dc7085e3126cc253d38d749bcbe842dd649
MD5 fa4501d5d2208c1c52bda0b55fa88517
BLAKE2b-256 2f95c53dfecb6052ea746541a4f4ade797bb5bdf7f9e22945d3d38f401f3fa04

See more details on using hashes here.

File details

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

File metadata

  • Download URL: fastapi_sendparcel-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 19.2 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 fastapi_sendparcel-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b6bb6e22f28aa8db10f34749cf3c8cc37006c9c827afb9ec8e89150690f1230e
MD5 b92470873c1805d03b6aea95d6a4db2e
BLAKE2b-256 94245f070295b6e6b4f0e594a2c0c0fc76cc9225fe0c0e6fa4344f35f088b0e8

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