Extensions SDK for SoftwareONE Marketplace Platform
Project description
SDK Usage
This guide shows how to build an extension package on top of mpt-extension-sdk.
Install The SDK
Install the package in a consumer project:
pip install mpt-extension-sdk
uv add mpt-extension-sdk
Extension Package Shape
The runtime auto-discovers exactly one top-level package in the working directory. That package must export:
app.pywithext_appsettings.pywithExtensionSettings
ExtensionSettings must inherit from
mpt_extension_sdk.settings.extension.BaseExtensionSettings.
Keep imports in app.py deterministic. The runtime imports ext_app to build
metadata before startup, so avoid network calls, filesystem I/O, or other heavy
side effects at module import time.
Create An Extension App
Start with ExtensionApp and the router family that matches your use case. The
extension app is the root SDK object for one extension package.
# mock_app/app.py
from mpt_extension_sdk import ExtensionApp
from mock_app.api.routes import orders_router
ext_app = ExtensionApp(prefix="/api/v2")
ext_app.include_router(orders_router)
# mock_app/api/routes.py
from mpt_extension_sdk.routing import EventRouter
orders_router = EventRouter(prefix="/events/orders")
The SDK also exposes APIRouter, ScheduleRouter, and PlugRouter.
EventRouter and APIRouter are mounted by the runtime. ScheduleRouter and
PlugRouter are modeled in the SDK contract but are not yet mounted by the
runtime or emitted into metadata.
Register Event Handlers
Use event(...) for non-task events:
from mpt_extension_sdk import EventRouter
orders_router = EventRouter(prefix="/events/orders")
@orders_router.event(
path="/purchase",
name="orders-purchase",
event="platform.commerce.order.purchased",
)
async def process_purchase(event, context):
"""Process a non-task order or agreement event."""
Use task(...) for task-backed events. The runtime starts, completes,
fails, or reschedules the Marketplace task around the handler execution.
from mpt_extension_sdk import EventRouter
orders_router = EventRouter(prefix="/events/orders")
@orders_router.task(
path="/change",
name="orders-change",
event="platform.commerce.order.created",
)
async def process_order_change(event, context):
"""Process a task-backed event."""
Within one router or app, each route name and path must be unique. Event
subscriptions must also be unique among event routes.
Register Authenticated API Handlers
Use APIRouter for authenticated extension API endpoints. The incoming request
must include an Authorization bearer token for the configured extension.
from mpt_extension_sdk import APIRouter
from mpt_extension_sdk.api import APIResponse
orders_api = APIRouter(prefix="/orders")
@orders_api.get(path="/{order_id}", name="orders-detail")
async def get_order(order_id: str, ctx):
order = await ctx.mpt_api_service.orders.get_by_id(order_id)
return APIResponse.ok(payload=order.to_dict())
For request bodies, pass a BaseSchema validator. The validated model is passed
to the handler through the body parameter.
from mpt_extension_sdk import APIRouter
from mpt_extension_sdk.api import APIResponse
from mpt_extension_sdk.schemas import BaseSchema
class SyncAgreementPayload(BaseSchema):
status: str
agreements_api = APIRouter(prefix="/agreements")
@agreements_api.post(
path="/{agreement_id}/sync",
name="agreements-sync",
body_validator=SyncAgreementPayload,
)
async def sync_agreement(agreement_id: str, payload: SyncAgreementPayload, ctx):
return APIResponse.accepted(payload={"id": agreement_id, "status": payload.status})
Event Context Resolution
The runtime builds the handler context from event.object.object_type in the
received payload:
Order->OrderContextAgreement->AgreementContext
This means a single extension app can register routes that receive either orders or agreements, and the SDK resolves the correct context family for each request at runtime.
Adapt The Execution Context
Use a context adapter when an extension needs to enrich the SDK-provided context with extension-specific fields or dependencies. The adapter must return the same context family it receives at runtime.
from dataclasses import dataclass, field
from typing import Self
from mpt_extension_sdk import EventRouter
from mpt_extension_sdk.pipeline import OrderContext
from mpt_extension_sdk.context import ContextAdapter
@dataclass(kw_only=True)
class CustomOrderContext(OrderContext, ContextAdapter):
notes: list[str] = field(default_factory=list)
@classmethod
def from_context(cls, ctx: OrderContext) -> Self:
return cls(**ctx.__dict__)
orders_router = EventRouter(prefix="/events/orders")
@orders_router.task(
path="/change",
name="orders-change",
event="platform.commerce.order.changed",
context_adapter_type=CustomOrderContext,
)
async def process_order_change(event, context):
assert isinstance(context, CustomOrderContext)
You can also configure context_adapter_type once on the router and override
it per route when needed.
Build A Processing Pipeline
Use BasePipeline, BaseStep, and typed execution contexts for multi-step
processing flows.
from typing import override
from mpt_extension_sdk.pipeline import BasePipeline, BaseStep, OrderContext
class ValidateOrderStep(BaseStep):
async def process(self, ctx: OrderContext) -> None:
"""Validate the order payload."""
class ProcessOrderStep(BaseStep):
async def process(self, ctx: OrderContext) -> None:
"""Run the main order logic."""
class PurchasePipeline(BasePipeline):
@property
@override
def steps(self) -> list[BaseStep]:
return [ValidateOrderStep(), ProcessOrderStep()]
The SDK exports both OrderContext and AgreementContext, and the runtime
hydrates the right one from event.object.object_type.
Run An Extension
Use the mpt-ext CLI command when running an extension built on top of the SDK:
mpt-ext run --local
mpt-ext run
mpt-ext meta generate
mpt-ext meta validate
mpt-ext run --localstarts the localFastAPI + uvicornruntime.mpt-ext runwritesmeta.yaml, registers the extension instance, and starts the platform runtime withmrok/ziticorn.mpt-ext meta generatewrites metadata derived fromext_app.to_meta_config().mpt-ext meta validatecompares the checked-inmeta.yamlwith generated metadata and writesmeta.generated.yamlwhen they differ.
Configure The Runtime
The SDK commonly relies on:
SDK_EXTENSION_URLSDK_EXTENSION_API_KEYSDK_EXTENSION_IDMPT_API_BASE_URL
Example configuration:
SDK_EXTENSION_URL=https://extensions.example.com
SDK_EXTENSION_API_KEY=<extension-api-key>
SDK_EXTENSION_ID=EXT-1234
MPT_API_BASE_URL=https://api.s1.show
SDK_LOCAL_PORT=8080
See configuration.md for the runtime environment-variable reference.
Use Marketplace Services
Execution contexts expose ctx.mpt_api_service as the main SDK entry point for
Marketplace reads and writes.
- service
get_*methods return typed SDK models create(...)methods accept a complete resource object, either as a plain mapping or as an SDK model withto_dict()update(..., attributes)accepts a partial attribute mapping for flexible updates- order transitions such as
complete(...),query(...), andfail(...)remain explicit service methods instead of generic updates
Event handler contexts use the JWT delivered in the event request
Authorization header. Authenticated API route contexts use the request auth
context to generate an account-scoped Marketplace token. The auth context is
also carried on SDK execution contexts as ctx.auth.
Example:
from mpt_extension_sdk.pipeline import BaseStep, OrderContext
class UpdateOrderStep(BaseStep):
async def process(self, ctx: OrderContext) -> None:
await ctx.mpt_api_service.orders.update(
ctx.order_id,
attributes={
"parameters": ctx.order.parameters.to_dict(),
},
)
await ctx.mpt_api_service.orders.complete(
ctx.order_id,
template={"id": "TPL-1", "name": "Completed"},
attributes={
"parameters": ctx.order.parameters.to_dict(),
"externalIds": ctx.order.external_ids.to_dict(),
},
)
What The SDK Provides
ExtensionAppand router-family classes for route registration and metadata- task and non-task event runtime wiring
- typed execution contexts, context adapters, and pipeline primitives
- Marketplace service helpers, settings discovery, and observability hooks
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 mpt_extension_sdk-6.1.0.tar.gz.
File metadata
- Download URL: mpt_extension_sdk-6.1.0.tar.gz
- Upload date:
- Size: 52.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.7.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
360f674c92bad3fd1316888c979581655172eabea6bac2b02ecb8332ee17b9e4
|
|
| MD5 |
31e490db0d133ae089f9062162f7cec2
|
|
| BLAKE2b-256 |
09d3d9eae07fa521e7d1365110d7467e0ec39801b6022b8582d7079e5bc92fe7
|
File details
Details for the file mpt_extension_sdk-6.1.0-py3-none-any.whl.
File metadata
- Download URL: mpt_extension_sdk-6.1.0-py3-none-any.whl
- Upload date:
- Size: 92.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.7.22
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6854cfb1b0bac7305e6c38cb10d1950e294778c2202bc9df4a0dbd185c5e53a
|
|
| MD5 |
005f4aa9bb8b5679bdea8719fc04c50c
|
|
| BLAKE2b-256 |
6369e1f4e7248d79d23eaff4bffb843789b8fd60ff3837630a990411b84d09b6
|