Python SDK
Package: msv3
Das Python SDK bietet synchronen Zugriff auf das MSV3 Smart Gateway ab Python 3.10+.
Installation
pip install msv3 Erfordert Python 3.10+. Das SDK hat keine Drittanbieter-Abhaengigkeiten -- nur die Standardbibliothek.
Grundeinrichtung
from msv3 import Msv3Client
client = Msv3Client(
api_key="pk_live_abc123", # oder pk_test_... fuer Sandbox
) | Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
api_key | str | erforderlich | Ihr Gateway-API-Schluessel |
base_url | str | https://api.msv3gateway.example.com | Ueberschreibung fuer Tests |
timeout | float | 30.0 | Anfrage-Timeout in Sekunden |
Zugangsdaten
MSV3-Zugangsdaten werden pro Aufruf uebergeben, nie im Client gespeichert:
from msv3 import Msv3Credentials
creds = Msv3Credentials(
wholesaler="noweda",
username="Now00079800",
password="your-password",
) Ressourcen
| Ressource | Attribut | Beschreibung |
|---|---|---|
ConnectionResource | client.connection | Grosshaendler-Konnektivitaet testen |
WholesalersResource | client.wholesalers | Grosshaendler auflisten und registrieren |
AvailabilityResource | client.availability | Echtzeit-Bestandsabfragen |
OrdersResource | client.orders | Bestellungen aufgeben und verfolgen |
ContractsResource | client.contracts | Vertragsdaten und Lieferfenster |
DeliveriesResource | client.deliveries | Lieferbenachrichtigungen |
ReturnsResource | client.returns | Retouren-Autorisierung und Ankuendigungen |
DocumentsResource | client.documents | PDF-Dokumente herunterladen |
WebhooksResource | client.webhooks | Webhook-Abonnement-Verwaltung |
Verbindung testen
result = client.connection.test(creds)
print(f"Verbunden mit {result.wholesaler} in {result.latency_ms}ms") Verfuegbarkeit
client.availability.check(creds, items)
result = client.availability.check(creds, items=[
{"pzn": "761271", "quantity": 5},
{"pzn": "10203595", "quantity": 1, "demand_type": "direct"},
])
for item in result.items:
if item.available:
d = item.deliveries[0]
print(f"PZN {item.pzn}: {d.quantity} Stueck, ETA {d.estimated_at}")
else:
print(f"PZN {item.pzn}: nicht verfuegbar")
if item.substitution:
print(f" Ersetzt durch PZN {item.substitution.replacement_pzn}") client.availability.bulk(creds, pzns)
result = client.availability.bulk(creds, pzns=[
"761271", "4211896", "10203595", "99999999"
])
print("Auf Lager:", result.available_pzns) Bestellungen
client.orders.create(creds, items, *, dry_run=False)
order = client.orders.create(creds, items=[
{"pzn": "761271", "quantity": 3, "delivery_preference": "backorder"},
{"pzn": "10203595", "quantity": 1},
])
print(f"Status: {order.status}") # 'confirmed'
print(f"Request ID: {order.request_id}")
for item in order.items:
print(f"PZN {item.pzn}: {item.quantity_confirmed}/{item.quantity_ordered}")
for d in item.deliveries:
print(f" {d.type}: {d.quantity} Stueck, Tour {d.tour}") Liefervorgaben: "normal" (Standard), "backorder", "grouped", "disposition"
Dry Run
order = client.orders.create(creds, items=[...], dry_run=True)
# order.status == "dry_run" Nachtmodus
if order.status == "queued_night_mode":
print("Grosshaendler im Nachtmodus. Bestellung vorgemerkt.") client.orders.status(creds, order_id)
status = client.orders.status(creds, order.request_id)
if status.status == "available":
print(f"Bestellung {status.order_id}: {len(status.items)} Artikel")
elif status.status == "expired":
print("Bestellantwort nicht mehr verfuegbar")
elif status.status == "unknown":
print("Grosshaendler kennt diese Bestell-ID nicht") Lieferungen
response = client.deliveries.list(creds)
for delivery in response.deliveries:
print(f"Lieferung {delivery.tracking_number} ({delivery.date})")
for item in delivery.items:
print(f" PZN {item.pzn}: {item.quantity_delivered} geliefert")
# Bestaetigen
tracking_numbers = [d.tracking_number for d in response.deliveries]
client.deliveries.confirm(creds, tracking_numbers=tracking_numbers) Webhooks
from msv3.types import WebhookCreateRequest
webhook = client.webhooks.create(
WebhookCreateRequest(
url="https://example.com/hooks/msv3",
wholesaler="noweda",
events=["delivery.received", "order.status_changed"],
)
)
# WICHTIG: signing_secret jetzt speichern!
print(f"Webhook ID: {webhook.id}")
print(f"Secret: {webhook.signing_secret}") Signatur-Verifizierung
from msv3.webhook_verify import verify_webhook_signature
@app.route("/hooks/msv3", methods=["POST"])
def handle_webhook():
payload = request.get_data() # Roh-Bytes - NICHT zuerst parsen
signature = request.headers.get("X-MSV3-Signature", "")
secret = os.environ["MSV3_WEBHOOK_SECRET"]
if not verify_webhook_signature(payload, signature, secret):
return "Invalid signature", 401
event = request.get_json()
print(f"Event: {event['event']} von {event['wholesaler']}")
return "OK", 200 Fehlerbehandlung
from msv3.errors import (
Msv3ApiError,
Msv3AuthError,
Msv3BadRequestError,
Msv3ProductUnavailableError,
Msv3RateLimitError,
Msv3WholesalerUnavailableError,
)
import time
try:
order = client.orders.create(creds, items=[...])
except Msv3AuthError as e:
print(f"Authentifizierung fehlgeschlagen: {e}")
except Msv3BadRequestError as e:
print(f"Ungueltige Anfrage: {e}")
except Msv3ProductUnavailableError as e:
print(f"Bestellung abgelehnt (Code {e.code}): {e}")
except Msv3RateLimitError as e:
wait = e.retry_after or 60
print(f"Rate-Limit. Warte {wait}s...")
time.sleep(wait)
except Msv3WholesalerUnavailableError as e:
print(f"Grosshaendler {e.wholesaler} nicht erreichbar: {e}")
except Msv3ApiError as e:
print(f"API-Fehler {e.status} [{e.type}]: {e}") | Attribut | Typ | Beschreibung |
|---|---|---|
status | int | HTTP-Statuscode |
type | str | Maschinenlesbarer Fehlercode |
message | str | Menschenlesbare Beschreibung |
wholesaler | str | None | Grosshaendler-ID |
code | str | None | Roher MSV3-Fehlercode |
retry_after | int | None | Wartezeit in Sekunden (nur 429) |
request_id | str | Request-ID aus dem X-Request-Id-Header |
Retry-Strategie
Das SDK wiederholt nicht automatisch. Implementieren Sie Retry-Logik in Ihrer Anwendung:
import time
from msv3.errors import Msv3WholesalerUnavailableError
def create_order_with_retry(client, creds, items, max_attempts=3):
for attempt in range(1, max_attempts + 1):
try:
return client.orders.create(creds, items=items)
except Msv3WholesalerUnavailableError as e:
if attempt < max_attempts:
delay = 2 ** (attempt - 1)
print(f"Versuch {attempt} fehlgeschlagen, erneut in {delay}s...")
time.sleep(delay)
else:
raise Vollstaendiges Beispiel
import os
from msv3 import Msv3Client, Msv3Credentials
from msv3.errors import Msv3ApiError
client = Msv3Client(api_key=os.environ["MSV3_API_KEY"])
creds = Msv3Credentials(
wholesaler="noweda",
username=os.environ["MSV3_USERNAME"],
password=os.environ["MSV3_PASSWORD"],
)
try:
# 1. Verbindung testen
connection = client.connection.test(creds)
print(f"Verbunden ({connection.latency_ms}ms)")
# 2. Verfuegbarkeit pruefen
availability = client.availability.check(creds, items=[
{"pzn": "761271", "quantity": 3},
])
if not availability.items[0].available:
print("PZN 761271 nicht verfuegbar")
raise SystemExit(1)
# 3. Bestellung mit Nachlieferung aufgeben
order = client.orders.create(creds, items=[
{"pzn": "761271", "quantity": 3, "delivery_preference": "backorder"},
])
print(f"Bestellstatus: {order.status}")
print(f"Request ID: {order.request_id}")
except Msv3ApiError as e:
print(f"MSV3-Fehler {e.status} [{e.type}]: {e}")
raise SystemExit(1)