Initial commit: Python SDK for Dragonchain (Prime)

Synchronous Python SDK modeled on prime-sdk-go. Provides DC1-HMAC-SHA256
auth, dataclass models, and resource clients for system, transaction,
transaction-type, smart-contract, and block endpoints, plus a YAML
credentials loader.
This commit is contained in:
2026-05-29 16:53:16 -04:00
commit 4a7d8b875a
15 changed files with 1686 additions and 0 deletions

145
prime_sdk/__init__.py Normal file
View File

@@ -0,0 +1,145 @@
"""Python SDK for interacting with Dragonchain (Prime) nodes.
Example — fire and forget (most use cases)::
from prime_sdk import DragonchainSDK, TransactionCreateRequest
client = DragonchainSDK(
"your-public-id",
"your-auth-key-id",
"your-auth-key",
"https://your-dragonchain-endpoint.com",
)
resp = client.transaction.create(
TransactionCreateRequest(
txn_type="my-transaction-type",
payload='{"message": "Hello Dragonchain"}',
)
)
print("Transaction ID:", resp.transaction_id)
# Done — no need to wait for a block.
Example — wait for block inclusion (interchain / Daria)::
import time
resp = client.transaction.create(
TransactionCreateRequest(
txn_type="my-transaction-type",
payload='{"message": "Hello Dragonchain"}',
)
)
while True:
txn = client.transaction.get(resp.transaction_id)
if txn.header.block_id:
print("Block ID:", txn.header.block_id)
break
time.sleep(2)
"""
from .block import BlockClient
from .client import CONTENT_TYPE_JSON, Client
from .contract import ContractClient
from .errors import DragonchainAPIError, DragonchainError
from .models import (
Block,
BlockProof,
GrpcConnectionInfo,
ListResponse,
ListTransactionsResponse,
SmartContract,
SmartContractCreateRequest,
SmartContractExecutionInfo,
SmartContractUpdateRequest,
SuccessResponse,
SystemStatus,
Transaction,
TransactionBulkRequest,
TransactionBulkResponse,
TransactionCreateRequest,
TransactionCreateResponse,
TransactionHeader,
TransactionListResponse,
TransactionProof,
TransactionType,
TransactionTypeCreateRequest,
TransactionTypeCreateResponse,
)
from .system import SystemClient
from .transaction import TransactionClient
from .transaction_type import TransactionTypeClient
__version__ = "0.1.0"
class DragonchainSDK:
"""Main SDK client for interacting with Dragonchain nodes.
Provides access to all API endpoints through specialized client instances:
``system``, ``transaction``, ``transaction_type``, ``contract``, ``block``.
"""
def __init__(
self,
public_id: str,
auth_key_id: str,
auth_key: str,
base_url: str,
):
"""Create a new Dragonchain SDK client.
Args:
public_id: Your Dragonchain public ID.
auth_key_id: Your authentication key ID.
auth_key: Your authentication key.
base_url: Base URL of your node (e.g. "https://chains.dragonchain.com").
"""
self._client = Client(public_id, auth_key_id, auth_key, base_url)
self.transaction = TransactionClient(self._client)
self.transaction_type = TransactionTypeClient(self._client)
self.contract = ContractClient(self._client)
self.block = BlockClient(self._client)
self.system = SystemClient(self._client)
def get_client(self) -> Client:
"""Return the underlying HTTP client (advanced use)."""
return self._client
__all__ = [
"DragonchainSDK",
"Client",
"CONTENT_TYPE_JSON",
"DragonchainError",
"DragonchainAPIError",
"BlockClient",
"ContractClient",
"SystemClient",
"TransactionClient",
"TransactionTypeClient",
# models
"Block",
"BlockProof",
"GrpcConnectionInfo",
"ListResponse",
"ListTransactionsResponse",
"SmartContract",
"SmartContractCreateRequest",
"SmartContractExecutionInfo",
"SmartContractUpdateRequest",
"SuccessResponse",
"SystemStatus",
"Transaction",
"TransactionBulkRequest",
"TransactionBulkResponse",
"TransactionCreateRequest",
"TransactionCreateResponse",
"TransactionHeader",
"TransactionListResponse",
"TransactionProof",
"TransactionType",
"TransactionTypeCreateRequest",
"TransactionTypeCreateResponse",
]

12
prime_sdk/block.py Normal file
View File

@@ -0,0 +1,12 @@
"""Block endpoints."""
from .client import Client
from .models import Block
class BlockClient:
def __init__(self, client: Client):
self._client = client
def get(self, block_id: str) -> Block:
return self._client.get(f"/api/v1/block/{block_id}", Block)

167
prime_sdk/client.py Normal file
View File

@@ -0,0 +1,167 @@
"""Low-level HTTP client implementing Dragonchain's DC1-HMAC-SHA256 auth.
This is a direct port of the Go SDK's ``client/client.go``. The signing scheme
must match the prime-node authorizer byte-for-byte: the signed message is six
fields joined by ``\\n`` (no trailing newline) in this exact order::
UPPER(method)
path # full path incl. /api/v1/..., no query string
publicId
timestamp
contentType
base64(sha256(body))
"""
import base64
import hashlib
import hmac
import json
import time
from typing import Any, Optional, Type, TypeVar
import requests
from .errors import DragonchainAPIError
T = TypeVar("T")
CONTENT_TYPE_JSON = "application/json"
DEFAULT_TIMEOUT = 30 # seconds
class Client:
"""Authenticated HTTP client for a single Dragonchain node."""
def __init__(
self,
public_id: str,
auth_key_id: str,
auth_key: str,
base_url: str,
timeout: int = DEFAULT_TIMEOUT,
):
self.public_id = public_id
self.auth_key_id = auth_key_id
self.auth_key = auth_key
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self._session = requests.Session()
# -- introspection helpers (mirror the Go accessors) --------------------
def internal_id(self) -> str:
return self.public_id
def get_public_id(self) -> str:
return self.public_id
def get_auth_key_id(self) -> str:
return self.auth_key_id
def endpoint(self) -> str:
return self.base_url
# -- auth ---------------------------------------------------------------
def _hmac_message(
self, method: str, path: str, timestamp: str, content_type: str, body: bytes
) -> str:
b64_content = base64.b64encode(hashlib.sha256(body).digest()).decode("ascii")
return "\n".join(
[
method.upper(),
path,
self.public_id,
timestamp,
content_type,
b64_content,
]
)
def _generate_auth_header(
self, method: str, path: str, timestamp: str, content_type: str, body: bytes
) -> str:
msg = self._hmac_message(method, path, timestamp, content_type, body)
digest = hmac.new(
self.auth_key.encode("utf-8"), msg.encode("utf-8"), hashlib.sha256
).digest()
b64_hmac = base64.b64encode(digest).decode("ascii")
return f"DC1-HMAC-SHA256 {self.auth_key_id}:{b64_hmac}"
# -- request plumbing ---------------------------------------------------
def _do_request(
self,
method: str,
path: str,
content_type: str = "",
body: Any = None,
response_cls: Optional[Type[T]] = None,
) -> Optional[T]:
if isinstance(body, (bytes, bytearray)):
body_bytes = bytes(body)
elif body is not None:
body_bytes = json.dumps(_to_jsonable(body)).encode("utf-8")
content_type = CONTENT_TYPE_JSON
else:
body_bytes = b""
timestamp = str(int(time.time()))
auth_header = self._generate_auth_header(
method, path, timestamp, content_type, body_bytes
)
headers = {
"Authorization": auth_header,
"Dragonchain": self.public_id,
"Timestamp": timestamp,
}
if content_type:
headers["Content-Type"] = content_type
url = self.base_url + path
resp = self._session.request(
method, url, data=body_bytes, headers=headers, timeout=self.timeout
)
resp_text = resp.text
if resp.status_code >= 400:
raise DragonchainAPIError(resp.status_code, resp_text.strip())
if response_cls is not None and resp_text:
parsed = json.loads(resp_text)
return response_cls.from_dict(parsed) # type: ignore[attr-defined]
return None
# -- verb helpers -------------------------------------------------------
def get(self, path: str, response_cls: Optional[Type[T]] = None) -> Optional[T]:
return self._do_request("GET", path, response_cls=response_cls)
def post(
self,
path: str,
content_type: str,
body: Any,
response_cls: Optional[Type[T]] = None,
) -> Optional[T]:
return self._do_request("POST", path, content_type, body, response_cls)
def put(
self,
path: str,
content_type: str,
body: Any,
response_cls: Optional[Type[T]] = None,
) -> Optional[T]:
return self._do_request("PUT", path, content_type, body, response_cls)
def delete(self, path: str, response_cls: Optional[Type[T]] = None) -> Optional[T]:
return self._do_request("DELETE", path, response_cls=response_cls)
def _to_jsonable(body: Any) -> Any:
"""Convert request models (which expose ``to_dict``) to JSON-ready data."""
if hasattr(body, "to_dict"):
return body.to_dict()
return body

49
prime_sdk/contract.py Normal file
View File

@@ -0,0 +1,49 @@
"""Smart-contract endpoints."""
from .client import CONTENT_TYPE_JSON, Client
from .models import (
ListResponse,
SmartContract,
SmartContractCreateRequest,
SmartContractUpdateRequest,
SuccessResponse,
)
class ContractClient:
def __init__(self, client: Client):
self._client = client
def create(self, req: SmartContractCreateRequest) -> SmartContract:
return self._client.post(
"/api/v1/contract", CONTENT_TYPE_JSON, req, SmartContract
)
def get(self, contract_id: str) -> SmartContract:
return self._client.get(f"/api/v1/contract/{contract_id}", SmartContract)
def list(self) -> ListResponse:
return self._client.get("/api/v1/contract", ListResponse)
def update(
self, contract_id: str, req: SmartContractUpdateRequest
) -> SuccessResponse:
return self._client.put(
f"/api/v1/contract/{contract_id}", CONTENT_TYPE_JSON, req, SuccessResponse
)
def upload(self, contract_id: str, filepath: str) -> SuccessResponse:
"""Upload smart-contract code from a local file (raw octet-stream body)."""
with open(filepath, "rb") as f:
file_content = f.read()
return self._client.put(
f"/api/v1/contract/{contract_id}/upload",
"application/octet-stream",
file_content,
SuccessResponse,
)
def delete(self, contract_id: str) -> SuccessResponse:
return self._client.delete(
f"/api/v1/contract/{contract_id}", SuccessResponse
)

127
prime_sdk/credentials.py Normal file
View File

@@ -0,0 +1,127 @@
"""YAML-based chain credentials, ported from ``credentials/credentials.go``.
The YAML format mirrors the Go SDK::
default: my-public-id
chains:
- name: my-chain
publicId: my-public-id
authKeyId: my-auth-key-id
authKey: my-auth-key
endpoint: https://chains.dragonchain.com
Note: unlike the Go version (which uses yaml.Node to preserve comments and
blank-line formatting), this loader uses PyYAML and does not round-trip
comments on ``save_config``.
"""
import os
from dataclasses import dataclass, field
from typing import List, Optional
import yaml
@dataclass
class ChainConfig:
name: str = ""
public_id: str = ""
auth_key_id: str = ""
auth_key: str = ""
endpoint: str = ""
@classmethod
def from_dict(cls, d: dict) -> "ChainConfig":
return cls(
name=d.get("name", ""),
public_id=d.get("publicId", ""),
auth_key_id=d.get("authKeyId", ""),
auth_key=d.get("authKey", ""),
endpoint=d.get("endpoint", ""),
)
def to_dict(self) -> dict:
return {
"name": self.name,
"publicId": self.public_id,
"authKeyId": self.auth_key_id,
"authKey": self.auth_key,
"endpoint": self.endpoint,
}
@dataclass
class Config:
default: str = ""
chains: List[ChainConfig] = field(default_factory=list)
# -- factory helpers ----------------------------------------------------
@classmethod
def from_dict(cls, d: dict) -> "Config":
return cls(
default=d.get("default", ""),
chains=[ChainConfig.from_dict(c) for c in (d.get("chains") or [])],
)
def to_dict(self) -> dict:
return {
"default": self.default,
"chains": [c.to_dict() for c in self.chains],
}
# -- lookups ------------------------------------------------------------
def get_default_chain(self) -> Optional[ChainConfig]:
"""Return the chain configuration for the default publicId, or None."""
return self.get_chain_by_public_id(self.default)
def get_chain_by_public_id(self, public_id: str) -> Optional[ChainConfig]:
for chain in self.chains:
if chain.public_id == public_id:
return chain
return None
def list_chains(self) -> List[str]:
"""Return all chain names."""
return [c.name for c in self.chains]
# -- mutations ----------------------------------------------------------
def add_chain(self, chain: ChainConfig) -> None:
self.chains.append(chain)
def set_default(self, public_id: str) -> None:
self.default = public_id
def delete_chain(self, public_id: str) -> None:
"""Delete a chain by publicId. Raises ValueError if it's the default."""
if public_id == self.default:
raise ValueError("cannot delete default chain")
self.chains = [c for c in self.chains if c.public_id != public_id]
# -- persistence --------------------------------------------------------
def save_config(self, file_path: str) -> None:
expanded = expand_path(file_path)
with open(expanded, "w", encoding="utf-8") as f:
yaml.safe_dump(self.to_dict(), f, sort_keys=False, default_flow_style=False)
def load_config(file_path: str) -> Config:
"""Read and parse a YAML configuration file."""
expanded = expand_path(file_path)
with open(expanded, "r", encoding="utf-8") as f:
data = yaml.safe_load(f) or {}
return Config.from_dict(data)
def load_config_from_string(yaml_content: str) -> Config:
"""Parse YAML configuration from a string."""
data = yaml.safe_load(yaml_content) or {}
return Config.from_dict(data)
def expand_path(path: str) -> str:
"""Expand environment variables and a leading ``~`` in a path."""
return os.path.expanduser(os.path.expandvars(path))

19
prime_sdk/errors.py Normal file
View File

@@ -0,0 +1,19 @@
"""Exceptions raised by the Dragonchain SDK."""
class DragonchainError(Exception):
"""Base class for all errors raised by the SDK."""
class DragonchainAPIError(DragonchainError):
"""Raised when the Dragonchain node returns an HTTP error (status >= 400).
Attributes:
status_code: The HTTP status code returned by the node.
body: The (trimmed) response body returned by the node.
"""
def __init__(self, status_code: int, body: str):
self.status_code = status_code
self.body = body
super().__init__(f"API error (status {status_code}): {body}")

395
prime_sdk/models.py Normal file
View File

@@ -0,0 +1,395 @@
"""Request and response models, ported from the Go SDK's ``models/models.go``.
Request models expose ``to_dict()`` which emits the exact JSON keys the node
expects and drops empty/optional values (the Go ``omitempty`` equivalent).
Response models expose a ``from_dict()`` classmethod that reads the exact JSON
keys returned by the node (snake_case for transaction/block models, camelCase
for contract/status models — preserved verbatim from the Go struct tags).
"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
CONTENT_TYPE_JSON = "application/json"
# --------------------------------------------------------------------------- #
# Transactions
# --------------------------------------------------------------------------- #
@dataclass
class TransactionCreateRequest:
txn_type: str
payload: str
tag: Optional[str] = None
version: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"txn_type": self.txn_type, "payload": self.payload}
if self.version:
d["version"] = self.version
if self.tag:
d["tag"] = self.tag
return d
@dataclass
class TransactionCreateResponse:
transaction_id: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionCreateResponse":
return cls(transaction_id=d.get("transaction_id", ""))
@dataclass
class TransactionBulkRequest:
transactions: List[TransactionCreateRequest] = field(default_factory=list)
def to_dict(self) -> Dict[str, Any]:
return {"transactions": [t.to_dict() for t in self.transactions]}
@dataclass
class TransactionBulkResponse:
transaction_ids: List[str] = field(default_factory=list)
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionBulkResponse":
return cls(transaction_ids=d.get("transaction_ids") or [])
@dataclass
class TransactionHeader:
tag: str = ""
dc_id: str = ""
txn_id: str = ""
invoker: str = ""
block_id: str = ""
txn_type: str = ""
timestamp: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionHeader":
return cls(
tag=d.get("tag", ""),
dc_id=d.get("dc_id", ""),
txn_id=d.get("txn_id", ""),
invoker=d.get("invoker", ""),
block_id=d.get("block_id", ""),
txn_type=d.get("txn_type", ""),
timestamp=d.get("timestamp", ""),
)
@dataclass
class TransactionProof:
full: str = ""
stripped: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionProof":
return cls(full=d.get("full", ""), stripped=d.get("stripped", ""))
@dataclass
class Transaction:
version: str = ""
header: TransactionHeader = field(default_factory=TransactionHeader)
proof: TransactionProof = field(default_factory=TransactionProof)
payload: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "Transaction":
return cls(
version=d.get("version", ""),
header=TransactionHeader.from_dict(d.get("header") or {}),
proof=TransactionProof.from_dict(d.get("proof") or {}),
payload=d.get("payload", ""),
)
@dataclass
class ListTransactionsResponse:
transactions: List[Transaction] = field(default_factory=list)
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "ListTransactionsResponse":
return cls(
transactions=[Transaction.from_dict(t) for t in (d.get("transactions") or [])]
)
# --------------------------------------------------------------------------- #
# Transaction types
# --------------------------------------------------------------------------- #
@dataclass
class TransactionTypeCreateRequest:
txn_type: str
version: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"txn_type": self.txn_type}
if self.version:
d["version"] = self.version
return d
@dataclass
class TransactionTypeCreateResponse:
success: bool = False
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionTypeCreateResponse":
return cls(success=bool(d.get("success", False)))
@dataclass
class TransactionType:
version: str = ""
created: int = 0
modified: int = 0
txn_type: str = ""
contract_id: str = ""
custom_indexes: List[Any] = field(default_factory=list)
active_since_block: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionType":
return cls(
version=d.get("version", ""),
created=d.get("created", 0),
modified=d.get("modified", 0),
txn_type=d.get("txn_type", ""),
contract_id=d.get("contract_id", ""),
custom_indexes=d.get("custom_indexes") or [],
active_since_block=d.get("active_since_block", ""),
)
@dataclass
class TransactionListResponse:
"""Response for listing transaction types (key: ``transactionTypes``)."""
transaction_types: List[TransactionType] = field(default_factory=list)
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "TransactionListResponse":
return cls(
transaction_types=[
TransactionType.from_dict(t) for t in (d.get("transactionTypes") or [])
]
)
# --------------------------------------------------------------------------- #
# Smart contracts
# --------------------------------------------------------------------------- #
@dataclass
class SmartContractCreateRequest:
environment: str
transaction_type: str
execution_order: str
environment_variables: Optional[Dict[str, str]] = None
secrets: Optional[Dict[str, str]] = None
remote: bool = False
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {
"environment": self.environment,
"transactionType": self.transaction_type,
"executionOrder": self.execution_order,
}
if self.environment_variables:
d["environmentVariables"] = self.environment_variables
if self.secrets:
d["secret"] = self.secrets
if self.remote:
d["remote"] = self.remote
return d
@dataclass
class SmartContractUpdateRequest:
enabled: bool = False
environment_variables: Optional[Dict[str, str]] = None
secrets: Optional[Dict[str, str]] = None
version: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"enabled": self.enabled}
if self.version:
d["version"] = self.version
if self.environment_variables:
d["environmentVariables"] = self.environment_variables
if self.secrets:
d["secret"] = self.secrets
return d
@dataclass
class GrpcConnectionInfo:
address: str = ""
api_key: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "GrpcConnectionInfo":
return cls(address=d.get("address", ""), api_key=d.get("apiKey", ""))
@dataclass
class SmartContractExecutionInfo:
type: str = ""
executable_path: str = ""
executable_working_directory: str = ""
executable_hash: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "SmartContractExecutionInfo":
return cls(
type=d.get("type", ""),
executable_path=d.get("executablePath", ""),
executable_working_directory=d.get("executableWorkingDirectory", ""),
executable_hash=d.get("executableHash", ""),
)
@dataclass
class SmartContract:
id: str = ""
created: int = 0
modified: int = 0
version: str = ""
environment: str = ""
transaction_type: str = ""
execution_order: str = ""
execution_info: Optional[SmartContractExecutionInfo] = None
env_vars: Dict[str, str] = field(default_factory=dict)
secrets: List[str] = field(default_factory=list)
grpc_connection_info: Optional[GrpcConnectionInfo] = None
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "SmartContract":
exec_info = d.get("executionInfo")
grpc = d.get("grpcConnectionInfo")
return cls(
id=d.get("id", ""),
created=d.get("created", 0),
modified=d.get("modified", 0),
version=d.get("version", ""),
environment=d.get("environment", ""),
transaction_type=d.get("transactionType", ""),
execution_order=d.get("executionOrder", ""),
execution_info=(
SmartContractExecutionInfo.from_dict(exec_info) if exec_info else None
),
env_vars=d.get("envVars") or {},
secrets=d.get("secrets") or [],
grpc_connection_info=(GrpcConnectionInfo.from_dict(grpc) if grpc else None),
)
# --------------------------------------------------------------------------- #
# Blocks
# --------------------------------------------------------------------------- #
@dataclass
class BlockProof:
scheme: str = ""
proof: str = ""
nonce: int = 0
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "BlockProof":
return cls(
scheme=d.get("scheme", ""),
proof=d.get("proof", ""),
nonce=d.get("nonce", 0),
)
@dataclass
class Block:
version: str = ""
id: str = ""
timestamp: str = ""
prev_id: str = ""
prev_proof: str = ""
transactions: List[str] = field(default_factory=list)
proof: BlockProof = field(default_factory=BlockProof)
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "Block":
return cls(
version=d.get("version", ""),
id=d.get("block_id", ""),
timestamp=d.get("timestamp", ""),
prev_id=d.get("prev_id", ""),
prev_proof=d.get("prev_proof", ""),
transactions=d.get("transactions") or [],
proof=BlockProof.from_dict(d.get("proof") or {}),
)
# --------------------------------------------------------------------------- #
# System / generic
# --------------------------------------------------------------------------- #
@dataclass
class SystemStatus:
id: str = ""
level: int = 0
url: str = ""
hash_algo: str = ""
scheme: str = ""
version: str = ""
encryption_algo: str = ""
indexing_enabled: bool = False
# Level 5 node fields
funded: str = ""
broadcast_interval: str = ""
network: str = ""
interchain_wallet: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "SystemStatus":
return cls(
id=d.get("id", ""),
level=d.get("level", 0),
url=d.get("url", ""),
hash_algo=d.get("hashAlgo", ""),
scheme=d.get("scheme", ""),
version=d.get("version", ""),
encryption_algo=d.get("encryptionAlgo", ""),
indexing_enabled=bool(d.get("indexingEnabled", False)),
funded=d.get("funded", ""),
broadcast_interval=d.get("broadcastInterval", ""),
network=d.get("network", ""),
interchain_wallet=d.get("interchainWallet", ""),
)
@dataclass
class SuccessResponse:
success: bool = False
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "SuccessResponse":
return cls(success=bool(d.get("success", False)))
@dataclass
class ListResponse:
items: List[Any] = field(default_factory=list)
total_count: int = 0
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "ListResponse":
return cls(items=d.get("items") or [], total_count=d.get("total_count", 0))

17
prime_sdk/system.py Normal file
View File

@@ -0,0 +1,17 @@
"""System endpoints (health, status)."""
from .client import Client
from .models import SystemStatus
class SystemClient:
def __init__(self, client: Client):
self._client = client
def health(self) -> None:
"""Check system health. Raises ``DragonchainAPIError`` if unhealthy."""
self._client.get("/api/v1/health")
def status(self) -> SystemStatus:
"""Get system status."""
return self._client.get("/api/v1/status", SystemStatus)

43
prime_sdk/transaction.py Normal file
View File

@@ -0,0 +1,43 @@
"""Transaction endpoints."""
from .client import CONTENT_TYPE_JSON, Client
from .models import (
ListTransactionsResponse,
Transaction,
TransactionBulkRequest,
TransactionBulkResponse,
TransactionCreateRequest,
TransactionCreateResponse,
)
class TransactionClient:
def __init__(self, client: Client):
self._client = client
def create(self, req: TransactionCreateRequest) -> TransactionCreateResponse:
"""Submit a new transaction and return immediately with the assigned ID.
This does NOT wait for the transaction to be included in a block. Block
processing happens asynchronously on a ~5-second cycle. If you need the
block ID (e.g. for interchain verification), poll ``get`` until
``header.block_id`` is populated.
"""
return self._client.post(
"/api/v1/transaction", CONTENT_TYPE_JSON, req, TransactionCreateResponse
)
def create_bulk(self, req: TransactionBulkRequest) -> TransactionBulkResponse:
"""Submit multiple transactions and return immediately with their IDs.
Like ``create``, this does not wait for block inclusion.
"""
return self._client.post(
"/api/v1/transaction/bulk", CONTENT_TYPE_JSON, req, TransactionBulkResponse
)
def get(self, transaction_id: str) -> Transaction:
return self._client.get(f"/api/v1/transaction/{transaction_id}", Transaction)
def list(self) -> ListTransactionsResponse:
return self._client.get("/api/v1/transaction/", ListTransactionsResponse)

View File

@@ -0,0 +1,38 @@
"""Transaction-type endpoints."""
from .client import CONTENT_TYPE_JSON, Client
from .models import (
SuccessResponse,
TransactionListResponse,
TransactionType,
TransactionTypeCreateRequest,
TransactionTypeCreateResponse,
)
class TransactionTypeClient:
def __init__(self, client: Client):
self._client = client
def create(
self, req: TransactionTypeCreateRequest
) -> TransactionTypeCreateResponse:
return self._client.post(
"/api/v1/transaction-type",
CONTENT_TYPE_JSON,
req,
TransactionTypeCreateResponse,
)
def get(self, txn_type: str) -> TransactionType:
return self._client.get(
f"/api/v1/transaction-type/{txn_type}", TransactionType
)
def list(self) -> TransactionListResponse:
return self._client.get("/api/v1/transaction-types", TransactionListResponse)
def delete(self, txn_type: str) -> SuccessResponse:
return self._client.delete(
f"/api/v1/transaction-type/{txn_type}", SuccessResponse
)