Skip to main content

Seller-facing tools for UnitySVC: HTTP SDK + usvc_seller CLI

Project description

unitysvc-sellers

Seller-facing tools for UnitySVC. Provides:

  • A typed Python HTTP SDK with sync (Client) and async (AsyncClient) facades for the seller API surface — services, promotions, service groups, documents, and end-to-end catalog upload. Defaults to the dedicated seller.staging.unitysvc.com/v1 subdomain; overridable via UNITYSVC_SELLER_API_URL for production or local development.
  • The usvc_seller CLI for organizing local seller catalogs and for managing remote services / promotions / service groups against the UnitySVC backend.

Install

pip install unitysvc-sellers

This pulls in unitysvc-core for the shared data models, JSON schemas, and generic file validator; plus httpx, attrs, typer, and rich.

Programmatic usage

from unitysvc_sellers import Client

client = Client(api_key="svcpass_...")  # or Client.from_env()

# List services
services = client.services.list(limit=50, status="active")
for s in services.data:
    print(s.name, s.id)

# Fetch full record
detail = client.services.get(service_id)

# Update service status
client.services.set_status(service_id, {"status": "ready"})

# Promotions (idempotent upsert by name)
client.promotions.upsert({
    "name": "summer-2026",
    "scope": {"customers": "*"},
    "pricing": {"type": "multiply", "factor": "0.80"},
    "status": "active",
})

# Service groups
group = client.groups.upsert({"name": "premium", "service_ids": [...]})

# Push an entire catalog directory
result = client.upload("./my-catalog", dryrun=False)
print(f"services: {result.services.success}/{result.services.total}")

Configuration

Source Default Override
API key (required) Client(api_key=...) / UNITYSVC_SELLER_API_KEY
Base URL https://seller.staging.unitysvc.com/v1 Client(base_url=...) / UNITYSVC_SELLER_API_URL
Timeout 30 s Client(timeout=...)

The seller context is encoded entirely in the API key (svcpass_...), so no separate seller_id argument is required.

Env var naming: env vars are namespaced to the seller role (UNITYSVC_SELLER_API_KEY, UNITYSVC_SELLER_API_URL) so a single host can run both the seller SDK and the future customer SDK side by side without collision — each picks up its own credentials.

URL layout: the SDK's generated paths are semantic resource paths (/services/{id}, /documents/{id}, /promotions, /service-groups) with no /seller wrapper. The seller scope is carried by the subdomain + API key. The same generated SDK works against any deployment layout without regeneration:

# Default: seller-scoped staging subdomain
Client()  # reads UNITYSVC_SELLER_API_URL or defaults to
          # https://seller.staging.unitysvc.com/v1

# Production (seller-scoped subdomain)
Client(base_url="https://seller.unitysvc.com/v1")

# Legacy combined surface where /seller is a path component
Client(base_url="https://api.unitysvc.com/v1/seller")

# Local development against a running backend
Client(base_url="http://localhost:8000/v1/seller")

Pagination

services.list, promotions.list, and groups.list use cursor-based pagination. Each call returns a CursorPage* response with data, next_cursor, and has_more:

# Single page
page = client.services.list(limit=50)
for s in page.data:
    print(s.name)

# Full iteration
cursor = None
while True:
    page = client.services.list(cursor=cursor, limit=100)
    for s in page.data:
        yield s
    if not page.has_more or not page.next_cursor:
        break
    cursor = page.next_cursor

The CLI list commands accept --cursor and --all (to auto-follow cursors and render the combined result).

Async client

The same API surface is exposed as AsyncClient for use in asyncio contexts (FastAPI, Starlette, Trio-via-anyio, scripts using asyncio.run):

import asyncio
from unitysvc_sellers import AsyncClient

async def main():
    async with AsyncClient(api_key="svcpass_...") as client:
        services = await client.services.list(limit=50)
        for s in services.data:
            print(s.name)

        await client.promotions.update(promo_id, {"status": "active"})

asyncio.run(main())

Each async method has the exact same signature as its sync counterpart on Client. The remote usvc_seller services|promotions|groups commands all use AsyncClient under the hood.

Errors

All errors are subclasses of unitysvc_sellers.SellerSDKError:

from unitysvc_sellers import (
    SellerSDKError,
    AuthenticationError,   # 401
    PermissionError,       # 403
    NotFoundError,         # 404
    ValidationError,       # 400, 422
    ConflictError,         # 409
    RateLimitError,        # 429
    ServerError,           # 5xx
    APIError,              # base for everything above
)

Each carries status_code, detail (parsed body if JSON), and response_body for debugging.

CLI: usvc_seller

The CLI has two sets of commands:

  • usvc_seller data ...local seller catalog operations (no network)
  • usvc_seller services|promotions|groups ...remote operations against the seller backend, all using the SDK's AsyncClient under the hood

Local commands

usvc_seller data validate [DATA_DIR]            # schema + catalog-layout validation
usvc_seller data format   [DATA_DIR]            # normalize JSON/TOML/MD files
usvc_seller data populate [DATA_DIR]            # run provider populate scripts
usvc_seller data show     provider|offering|listing|service NAME
usvc_seller data list     providers|sellers|offerings|listings|services [DATA_DIR]
usvc_seller data list-tests                     # list local code-example / connectivity tests
usvc_seller data run-tests                      # run them locally
usvc_seller data show-test SERVICE              # show last local test result
usvc_seller data upload   [DATA_DIR]            # upload services + promotions + groups
        [--api-key svcpass_...]                 #   defaults to $UNITYSVC_SELLER_API_KEY
        [--base-url https://...]                #   defaults to $UNITYSVC_SELLER_API_URL or staging
        [--type services|promotions|groups]     #   restrict to one resource kind
        [--dryrun]                              #   validate against backend without persisting

Remote commands (require $UNITYSVC_SELLER_API_KEY or --api-key)

# Services
usvc_seller services list [--status STATUS] [--name NAME] [--provider NAME]
                          [--fields id,name,...] [--format table|json]
usvc_seller services show SERVICE_ID [--format table|json]
usvc_seller services submit    SERVICE_IDS... | --all [--provider NAME] [--yes]
usvc_seller services withdraw  SERVICE_IDS... | --all [--provider NAME] [--yes]
usvc_seller services deprecate SERVICE_IDS... | --all [--provider NAME] [--yes]
usvc_seller services delete    SERVICE_IDS... | --all [--status STATUS]
                              [--provider NAME] [--dryrun] [--yes]
usvc_seller services update SERVICE_ID
        [--set-routing-var key=value | '{json}']        (repeatable)
        [--remove-routing-var key]                       (repeatable)
        [--load-routing-vars path/to.json]
        [--set-price key=value | '{json}' | NUMBER]      (repeatable)
        [--remove-price-field key]                       (repeatable)

# Document tests (registered under services for parity with the legacy CLI)
usvc_seller services list-tests   [SERVICE_ID] [--all] [--status STATUS]
                                  [--format table|json]
usvc_seller services show-test    DOCUMENT_ID [--format table|json]
usvc_seller services run-tests    SERVICE_ID [--document-id DOC_ID] [--force]
usvc_seller services skip-test    DOCUMENT_ID
usvc_seller services unskip-test  DOCUMENT_ID

# Promotions
usvc_seller promotions list   [--format table|json]
usvc_seller promotions show     NAME_OR_ID [--format table|json]
usvc_seller promotions activate NAME_OR_ID
usvc_seller promotions pause    NAME_OR_ID
usvc_seller promotions delete   NAME_OR_ID [--force]

# Service groups
usvc_seller groups list   [--status STATUS] [--format table|json]
usvc_seller groups show    NAME_OR_ID [--format table|json]
usvc_seller groups delete  NAME_OR_ID [--force]

promotions activate / pause are sugar over PATCH /promotions/{id} with a status field — the backend consolidated the legacy /activate and /pause routes.

The legacy usvc services dedup command is not ported because the backing endpoint was removed; use services delete --all --status draft instead.

The legacy usvc_seller groups refresh command is not ported either. Dynamic group membership is now refreshed automatically by a background worker whenever a group is mutated, so there's no manual refresh step for sellers to invoke.

Layout

src/unitysvc_sellers/
├── client.py            # Client (sync) facade
├── aclient.py           # AsyncClient (async) facade
├── exceptions.py        # SellerSDKError + status-code subclasses
├── _http.py             # internal: unwrap generated Response → typed model or APIError
├── resources/
│   ├── services.py      # client.services.{list,get,upload,set_status,...}
│   ├── promotions.py    # client.promotions.{list,get,upsert,update,delete}
│   ├── groups.py        # client.groups.{list,get,upsert,update,delete}
│   ├── documents.py     # client.documents.{get,execute,update_test}
│   ├── aservices.py     # async mirror of services.py
│   ├── apromotions.py   # async mirror of promotions.py
│   ├── agroups.py       # async mirror of groups.py
│   ├── adocuments.py    # async mirror of documents.py
│   └── upload.py        # high-level upload_directory(client, path)
├── _generated/          # openapi-python-client output (do not edit by hand)
│   ├── client.py        #   AuthenticatedClient (httpx + attrs, sync + async)
│   ├── api/seller_services/       #   services_list, services_get, ...
│   ├── api/seller_promotions/     #   promotions_list, promotions_upsert, ...
│   ├── api/seller_service_groups/ #   groups_list, groups_upsert, ...
│   ├── api/seller_documents/      #   documents_get, documents_execute, ...
│   ├── models/          #   one model per schema component
│   └── ...
├── commands/            # Typer command groups for the remote CLI
│   ├── _helpers.py      #   run_async, async_client, model_list,
│   │                    #     resolve_promotion, resolve_service_id, ...
│   ├── services.py      #   `usvc_seller services {list,show,submit,...}`
│   ├── tests.py         #   `usvc_seller services {list,show,run,skip,unskip}-test`
│   ├── promotions.py    #   `usvc_seller promotions {list,show,activate,pause,delete}`
│   └── groups.py        #   `usvc_seller groups {list,show,delete}`
├── cli.py               # `usvc_seller` Typer entry point
├── data.py              # `usvc_seller data` command group (local)
├── _cli_upload.py       # `usvc_seller data upload` Typer wrapper
├── validator.py         # seller DataValidator (extends unitysvc_core.validator)
├── format_data.py       # `usvc_seller data format`
├── populate.py          # `usvc_seller data populate`
├── example.py           # `usvc_seller data {list,run,show}-test` (local)
├── list.py              # `usvc_seller data list *`
├── output.py            # shared Rich output helpers
└── utils.py             # seller-only helpers + re-exports from unitysvc_core.utils

Regenerating the API client

The low-level client under src/unitysvc_sellers/_generated/ is produced by openapi-python-client from a filtered copy of the backend OpenAPI spec at openapi.json. To regenerate after a backend change:

# Requires a sibling checkout of unitysvc/unitysvc with backend/.venv set up
./scripts/generate_client.sh ../unitysvc

This script:

  1. Dumps /v1/openapi.json from the backend's running app via scripts/dump_spec.py.
  2. Filters to seller-tagged operations and sanitizes schema names that contain characters openapi-python-client cannot parse (e.g. strips the auto-generated pydantic title from anonymous inline object schemas to avoid Pricing / Terms collisions).
  3. Runs openapi-python-client generate with the config in scripts/openapi-python-client.yml.

The hand-written facades in unitysvc_sellers/{client,resources}.py should rarely change when the spec is regenerated; only the operation modules under _generated/api/seller/ and the models under _generated/models/ get refreshed.

History

This package was split out of unitysvc-services (see issue #99). Shared types + schemas live in unitysvc-core; seller CLI, the catalog HTTP SDK, and seller-specific catalog utilities live here.

Roadmap

  • unitysvc_sellers.builders — catalog-builder helpers (populate_from_iterator, render_template_file, etc.) for unitysvc-services-* data repositories.
  • Attachment-bytes upload — once the backend defines its replacement for the old /seller/documents/upload-attachment endpoint, the client.upload(...) orchestrator will inline binary file content again instead of requiring external_url references.

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

unitysvc_sellers-0.1.1.tar.gz (147.6 kB view details)

Uploaded Source

Built Distribution

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

unitysvc_sellers-0.1.1-py3-none-any.whl (248.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: unitysvc_sellers-0.1.1.tar.gz
  • Upload date:
  • Size: 147.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for unitysvc_sellers-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c0f8b77041aa53e6c6220fa9bf17610e0349ee0854f42c5489bcacd9424fbe35
MD5 fec6178c4d883a2049566bda48c8e22f
BLAKE2b-256 ddd8a528d63c74e819785f200886479ef4029f60da24b57a9edab770df5f88a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for unitysvc_sellers-0.1.1.tar.gz:

Publisher: publish.yml on unitysvc/unitysvc-sellers

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

File hashes

Hashes for unitysvc_sellers-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5726d9b3abea10196f911cc0d6eafc80aec2e57e2a054b926774124d292203e2
MD5 902b9330dca5b4dc6844f014e5576a8c
BLAKE2b-256 91f42ba897b39c116be1d3259f6085af6ee09dc8d13f81efe7c89c58cb1d1966

See more details on using hashes here.

Provenance

The following attestation bundles were made for unitysvc_sellers-0.1.1-py3-none-any.whl:

Publisher: publish.yml on unitysvc/unitysvc-sellers

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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