Skip to main content

Async Python wrapper for the ShipStation API

Project description

ShipStation Interaction / Automation

Type-Check Linting Code style: black Validation: Pydantic v2 Python 3.11.14 PyPI - Version PyPI - License

Async Python client for ShipStation v1 and v2 APIs with an emphasis on typing.

Install

pip

pip install AsyncShipStation

Manual

git clone git@github.com:sudoDeVinci/AsyncShipStation.git
cd AsyncShipStation
pip install -r requirements.txt

Quick Start

Create a connection, then pass it to the portal you want to use.

import asyncio

from AsyncShipStation import ShipStationClient, ShipmentPortal


async def main() -> None:
    connection = await ShipStationClient.connect(
        v2_key="your_v2_api_key",
        v1_key="your_v1_api_key",
        v1_secret="your_v1_secret",
    )

    async with ShipStationClient.scoped_client(connection=connection, version="v2"):
        status, shipments = await ShipmentPortal.list(
            connection,
            page_size=10,
            page=1,
        )
        print(status, shipments)


if __name__ == "__main__":
    asyncio.run(main())

The library comes pre-configured with 'sensible' default values for connections, but you can configure these parameters by passing a ConnectionConfig object with the parameters you would like changed.

config  = ConnectionConfig(
    version="v2",
    timeout=30,
    max_connections=20,
    max_keepalive_connections=10,
    http2=False,
    retries=4,
    user_agent="your_custom_UA_string",
)
connection = await ShipStationClient.connect(
    v2_key="your_v2_api_key",
    v1_key="your_v1_api_key",
    v1_secret="your_v1_secret",
    config=config,
    )

NOTE: A connection's config is tied to its identity. Two connections to the ssame credentials, if using different configs, will be treated as separate connections.

Client Lifecycle

Use the async context manager w/ existing connection object

Use scoped_client() when you want the connection lifecycle handled for you.

import asyncio
import os

from dotenv import load_dotenv

from AsyncShipStation import ShipStationClient, ShipmentPortal

load_dotenv()
V2_API_KEY: str | None = os.getenv("SHIP_STATION_V2")
V1_API_KEY: str | None = os.getenv("SHIP_STATION_V1")
V1_SECRET: str | None = os.getenv("SHIP_STATION_SECRET")


async def main() -> None:
    connection = await ShipStationClient.connect(
        v2_key=V2_API_KEY or "",
        v1_key=V1_API_KEY,
        v1_secret=V1_SECRET,
    )

    async with ShipStationClient.scoped_client(connection=connection, version="v2"):
        status, shipments = await ShipmentPortal.list(connection, page_size=10, page=1)
        print(status, shipments)


if __name__ == "__main__":
    asyncio.run(main())

Use the async context manager w/o existing connection object

If you don't need to reuse the connection object, you can skip the explicit configuration step and pass the credentials directly to scoped_client().

import asyncio
import os

from dotenv import load_dotenv

from AsyncShipStation import ShipStationClient, ShipmentPortal

load_dotenv()
V2_API_KEY: str | None = os.getenv("SHIP_STATION_V2")
V1_API_KEY: str | None = os.getenv("SHIP_STATION_V1")
V1_SECRET: str | None = os.getenv("SHIP_STATION_SECRET")


async def main() -> None:
    connection = await ShipStationClient.connect(
        
    )

    async with ShipStationClient.scoped_client(
        v2_key=V2_API_KEY or "",
        v1_key=V1_API_KEY,
        v1_secret=V1_SECRET, version="v2"
    ):
        status, shipments = await ShipmentPortal.list(connection, page_size=10, page=1)
        print(status, shipments)


if __name__ == "__main__":
    asyncio.run(main())

Start and close explicitly

Use start() and close() if you want to manage the lifecycle yourself.

import asyncio
import os

from dotenv import load_dotenv

from AsyncShipStation import ShipStationClient, ShipmentPortal

load_dotenv()
V2_API_KEY: str | None = os.getenv("SHIP_STATION_V2")
V1_API_KEY: str | None = os.getenv("SHIP_STATION_V1")
V1_SECRET: str | None = os.getenv("SHIP_STATION_SECRET")


async def main() -> None:
    connection = await ShipStationClient.connect(
        v2_key=V2_API_KEY or "",
        v1_key=V1_API_KEY,
        v1_secret=V1_SECRET,
    )

    await ShipStationClient.start(connection=connection, version="v2")
    try:
        status, shipments = await ShipmentPortal.list(connection, page_size=10, page=1)
        print(status, shipments)
    finally:
        await ShipStationClient.close(connection=connection, version="v2")


if __name__ == "__main__":
    asyncio.run(main())

Concurrent Requests

A single connection is meant to be shared across concurrent requests.

import asyncio

from AsyncShipStation import (
    BatchPortal,
    LabelPortal,
    ShipmentPortal,
    ShipStationClient,
)


async def main() -> None:
    connection = await ShipStationClient.connect(
        v2_key="your_v2_api_key",
        v1_key="your_v1_api_key",
        v1_secret="your_v1_secret",
    )

    async with ShipStationClient.scoped_client(connection=connection, version="v2"):
        results = await asyncio.gather(
            ShipmentPortal.list(connection, page_size=10, page=1),
            BatchPortal.list(connection, page_size=10, page=1),
            LabelPortal.list(connection, page_size=10, page=1),
        )

    for status, data in results:
        if status in (200, 201, 207):
            print(f"Success :: {data}")
        else:
            print(f"Error :: {data}")


if __name__ == "__main__":
    asyncio.run(main())

Connection Lookup

If you need to retrieve a connection from the pool later, use connection.uid.

import asyncio

from AsyncShipStation import ShipStationClient, ShipmentPortal


async def main() -> None:
    connection = await ShipStationClient.connect(
        v2_key="your_v2_api_key",
        v1_key="your_v1_api_key",
        v1_secret="your_v1_secret",
    )

    async with ShipStationClient.scoped_client(
        connection_hash=connection.pool_key,
        version="v2",
    ) as scoped_connection:
        status, shipments = await ShipmentPortal.list(
            scoped_connection,
            page_size=10,
            page=1,
        )
        print(status, shipments)


if __name__ == "__main__":
    asyncio.run(main())

Rate Limiting

Accounts that send too many requests in quick succession will receive a 429 Too Many Requests response with a Retry-After header that tells you how long to wait.

ShipStation bulk operation endpoints count as a single request.

TODO

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

asyncshipstation-0.2.1.6.tar.gz (50.7 kB view details)

Uploaded Source

Built Distribution

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

asyncshipstation-0.2.1.6-py3-none-any.whl (107.4 kB view details)

Uploaded Python 3

File details

Details for the file asyncshipstation-0.2.1.6.tar.gz.

File metadata

  • Download URL: asyncshipstation-0.2.1.6.tar.gz
  • Upload date:
  • Size: 50.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for asyncshipstation-0.2.1.6.tar.gz
Algorithm Hash digest
SHA256 0e423cf533f417bf8749c5ab80163691f4af0c27f591b4c9d64960b74521698a
MD5 e62b6b4bccdff68fccf6864d2dc1652c
BLAKE2b-256 eb33c408ff699416c5c6b4785e77da0201f52b7814e0636de0cc2a86dba0a801

See more details on using hashes here.

File details

Details for the file asyncshipstation-0.2.1.6-py3-none-any.whl.

File metadata

File hashes

Hashes for asyncshipstation-0.2.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 479de58774680371c193a4de65bfa02008d1bba3f36e59759ce5c05a87091ba3
MD5 3d20c9510ef187dddfc030c8f25f9c4b
BLAKE2b-256 0eec56046ed7c3f1b9d534dda07e8c2ff90f991bdacdb2f5d850df03a70a7c84

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