CLI/SDK Implementation Guide
Guide for implementing Backend.AI client SDK and CLI with session management, API functions, and Click commands.
Purpose
SDK (Software Development Kit):
- Python library for Backend.AI REST API
- Programmatic access for integrations and automation
- Async-first with sync wrapper for CLI compatibility
CLI (Command Line Interface):
- User-facing commands for Backend.AI operations
- Built on top of SDK functions
- Supports console and JSON output modes
When to use:
- SDK: Building integrations, automation scripts, web dashboards
- CLI: Interactive administration, shell scripts, CI/CD pipelines
- Direct API: Only when SDK doesn't support the operation yet
Architecture Overview
Layers: CLI → SDK → REST API → Manager
Flow: CLI commands call SDK functions through Session, which makes HTTP requests to REST API endpoints.
CLI Layer:
ExtendedCommandGroup - Command groups with aliases and interrupt handling
LazyGroup - Lazy loading for faster startup
CLIContext - Shared state (config, output mode)
@pass_ctx_obj - CLIContext injection decorator
FieldSpec - Output field definitions
BaseOutputHandler - Console/JSON formatters
SDK Layer:
BaseFunction - Metaclass for API function classes
@api_function - Decorator for API methods
AsyncSession - Primary async HTTP session
Session - Sync wrapper for CLI
api_session - ContextVar for session context
Data Layer:
- Pydantic models - Request/Response DTOs (future standard)
- attrs classes - Legacy DTOs (current usage in
client/cli/types.py)
- New code should use Pydantic (
common/dto/)
Config Layer:
APIConfig - Environment variables and .env file
CLIContext - CLI state and output handler
SDK Implementation Patterns
Session Management
AsyncSession (Primary):
- Async context manager for API operations
- Manages HTTP client lifecycle, authentication tokens
- Uses
api_session ContextVar for context propagation
Session (Sync Wrapper):
- Synchronous wrapper for CLI commands
- Creates async event loop internally
- Same interface as AsyncSession
See:
client/session.py - Session and AsyncSession implementation
client/request.py - HTTP request handling
client/config.py - APIConfig for environment variables
API Function Pattern
@api_function decorator:
- Wraps instance methods as API functions
- Retrieves session from
api_session ContextVar
- Handles async/sync execution
- Provides consistent error handling
BaseFunction metaclass:
- Creates function group instances bound to session
- Examples:
session.FairShare, session.Admin
Standard operations:
create_* - POST requests
get_* - GET single item
search_* - GET collection with filters
update_* - PATCH requests
delete_* - DELETE requests
purge_* - Permanent deletion
See:
client/func/base.py - BaseFunction and @api_function
client/func/fair_share.py - Complete implementation example
Pydantic Models (Future Standard)
Current state:
- attrs - Current usage in
client/cli/types.py
- Pydantic - Future standard in
common/dto/
- New code should use Pydantic
Why Pydantic:
- Runtime type validation
- Automatic JSON serialization/deserialization
- IDE autocomplete and type checking
- Better error messages
Usage:
model_dump(mode="json", exclude_none=True) - To JSON dict
model_validate(data) - From JSON dict
- Handles nested models automatically
See:
client/func/fair_share.py - Pydantic usage in SDK
common/dto/fair_share.py - Shared DTO definitions
SDK Implementation Flow
Standard pattern:
- Define request/response Pydantic models
- Create method with @api_function decorator
- Build Request object with endpoint and data
- Fetch JSON response from session
- Parse with
Response.model_validate(data)
- Return typed result
See complete example:
client/func/fair_share.py - All standard operations (create, get, search, update, delete)
CLI Implementation Patterns
Click Command Structure
Command organization:
ExtendedCommandGroup - Enhanced Click group with interrupt handling
LazyGroup - Defers module imports until needed
@pass_ctx_obj - Type-safe CLIContext injection
See:
client/cli/main.py - Main entry and command groups
client/cli/extensions.py - @pass_ctx_obj decorator
client/cli/fair_share/__init__.py - LazyGroup usage
Output Handlers
FieldSpec:
- Defines displayable fields for a model
- Specifies field name, human-readable label, formatters
- Supports nested fields (dot notation) and custom formatters
BaseOutputHandler:
print_item() - Single item display
print_list() - Collection display
print_error() - Error messages
- Implementations: ConsoleOutputHandler (table), JSONOutputHandler
See:
client/output/types.py - FieldSpec and BaseOutputHandler protocol
client/output/fields.py - Field utilities
client/cli/fair_share/commands.py - FieldSpec definitions and usage
CLI + SDK Integration
Pattern:
- Click command → Session context → SDK function → REST API
- Catch
BackendAIError exceptions
- Use
ctx.output.print_error() for consistent formatting
- Exit with appropriate ExitCode
Error handling:
- All exceptions inherit from
BackendAIError
- Exit codes from
ExitCode enum (OK=0, FAILURE=1, INVALID_ARGUMENT=2, PERMISSION_DENIED=3)
ctx.output.print_error() handles both console and JSON modes
See:
client/cli/fair_share/commands.py - Complete CLI integration
client/cli/types.py - CLIContext and ExitCode
client/exceptions.py - Exception hierarchy
Common Patterns
| Pattern |
SDK |
CLI |
| Session |
async with AsyncSession() |
with Session() (sync wrapper) |
| Request |
Request("POST", "/endpoint", json={...}) |
SDK handles internally |
| Response |
Response.model_validate(data) |
SDK returns parsed model |
| Output |
Return Pydantic model |
ctx.output.print_item() or print_list() |
| Error |
Raise BackendAIError subclass |
print_error() + sys.exit(ExitCode) |
| Config |
APIConfig() reads environment |
Passed via CLIContext |
| Async |
Always async (async def, await) |
Sync wrapper handles internally |
Implementation Checklist
When implementing new CLI/SDK feature:
✅ Implement REST API (/api-guide)
- Handler with standard operations
- Proper error responses
✅ Define Pydantic models
- Request DTOs in
common/dto/
- Response DTOs shared with API layer
✅ Implement SDK function
- Add method to appropriate function group
- Use
@api_function decorator
- Handle all operations (create, get, search, update, delete)
✅ Define FieldSpec
- List displayable fields
- Add formatters for complex types
- Consider both console and JSON output
✅ Implement CLI commands
- Add Click command group
- Map options/arguments to SDK calls
- Handle errors with proper exit codes
✅ Write tests (/test-guide)
- SDK: Mock HTTP responses (pytest-aiohttp)
- CLI: Use CliRunner, mock SDK functions (not HTTP)
- Verify request/response serialization
✅ Update documentation
- Add usage examples to README
- Document new CLI commands
Testing
SDK tests:
- Mock HTTP responses with
pytest-aiohttp
- Test request serialization and response parsing
- Verify Pydantic model validation
CLI tests:
- Use Click's
CliRunner for command invocation
- Mock SDK functions (not HTTP layer)
- Test output formatting and exit codes
See:
/test-guide skill for testing workflow
tests/unit/client/ - Test examples
Reference Files
Complete implementations:
client/func/fair_share.py - Full SDK with Pydantic models
client/cli/fair_share/commands.py - Full CLI with all patterns
Architecture components:
client/session.py - Session and AsyncSession
client/func/base.py - @api_function and BaseFunction
client/cli/main.py - CLI entry point
client/cli/extensions.py - @pass_ctx_obj decorator
client/output/types.py - FieldSpec and output handlers
client/config.py - APIConfig
client/cli/types.py - CLIContext and ExitCode
client/exceptions.py - Exception hierarchy
V2 SDK/CLI (New Pattern)
All new SDK/CLI work MUST use the v2 pattern.
V2 SDK Client (client/v2/domains_v2/)
- Inherits
BaseDomainClient, receives BackendAIAuthClient via constructor
- Uses v2 DTOs from
common/dto/manager/v2/ exclusively
typed_request() handles serialization and response parsing
- Registry:
V2ClientRegistry (client/v2/v2_registry.py) with @cached_property per domain
V2 CLI (client/cli/v2/)
Command pattern: ./bai [admin] {entity} [{sub-entity}] {operation}
admin_ SDK methods → commands under ./bai admin {entity} ...
- Non-admin methods →
./bai {entity} ...
- Entity names are singular (domain, user, agent)
- Each entity has its own directory with
__init__.py + commands.py
- Admin commands in
admin/{entity}.py
- Sub-entities as separate Click sub-groups (e.g., revision, channel, role)
- Construct Pydantic DTOs with explicit field arguments (never kwargs unpacking)
- Filter options are domain-specific CLI args (
--name-contains, --status, etc.)
--order-by field:direction supports multiple values
- Config from
~/.backend.ai/ via load_v2_config() → V2ConnectionConfig dataclass
V2 Config (~/.backend.ai/)
config.toml — endpoint, endpoint_type, api_version
credentials.toml — access_key, secret_key
session/cookie.dat — webserver session cookie (login/logout)
Related skills:
/api-guide - REST API implementation (prerequisite)
/test-guide - Testing workflow
/local-dev - Service management and CLI testing
1---2name: cli-sdk-guide3description: Guide for implementing Backend.AI client SDK and CLI (Session, BaseFunction, @api_function, Click commands, Pydantic models, FieldSpec, output handlers, APIConfig, testing)4---56# CLI/SDK Implementation Guide78Guide for implementing Backend.AI client SDK and CLI with session management, API functions, and Click commands.910## Purpose1112**SDK (Software Development Kit):**13- Python library for Backend.AI REST API14- Programmatic access for integrations and automation15- Async-first with sync wrapper for CLI compatibility1617**CLI (Command Line Interface):**18- User-facing commands for Backend.AI operations19- Built on top of SDK functions20- Supports console and JSON output modes2122**When to use:**23- SDK: Building integrations, automation scripts, web dashboards24- CLI: Interactive administration, shell scripts, CI/CD pipelines25- Direct API: Only when SDK doesn't support the operation yet2627## Architecture Overview2829**Layers:** CLI → SDK → REST API → Manager3031**Flow:** CLI commands call SDK functions through Session, which makes HTTP requests to REST API endpoints.3233**CLI Layer:**34- `ExtendedCommandGroup` - Command groups with aliases and interrupt handling35- `LazyGroup` - Lazy loading for faster startup36- `CLIContext` - Shared state (config, output mode)37- `@pass_ctx_obj` - CLIContext injection decorator38- `FieldSpec` - Output field definitions39- `BaseOutputHandler` - Console/JSON formatters4041**SDK Layer:**42- `BaseFunction` - Metaclass for API function classes43- `@api_function` - Decorator for API methods44- `AsyncSession` - Primary async HTTP session45- `Session` - Sync wrapper for CLI46- `api_session` - ContextVar for session context4748**Data Layer:**49- **Pydantic models** - Request/Response DTOs (future standard)50- **attrs classes** - Legacy DTOs (current usage in `client/cli/types.py`)51- New code should use Pydantic (`common/dto/`)5253**Config Layer:**54- `APIConfig` - Environment variables and .env file55- `CLIContext` - CLI state and output handler5657## SDK Implementation Patterns5859### Session Management6061**AsyncSession (Primary):**62- Async context manager for API operations63- Manages HTTP client lifecycle, authentication tokens64- Uses `api_session` ContextVar for context propagation6566**Session (Sync Wrapper):**67- Synchronous wrapper for CLI commands68- Creates async event loop internally69- Same interface as AsyncSession7071**See:**72- `client/session.py` - Session and AsyncSession implementation73- `client/request.py` - HTTP request handling74- `client/config.py` - APIConfig for environment variables7576### API Function Pattern7778**@api_function decorator:**79- Wraps instance methods as API functions80- Retrieves session from `api_session` ContextVar81- Handles async/sync execution82- Provides consistent error handling8384**BaseFunction metaclass:**85- Creates function group instances bound to session86- Examples: `session.FairShare`, `session.Admin`8788**Standard operations:**89- `create_*` - POST requests90- `get_*` - GET single item91- `search_*` - GET collection with filters92- `update_*` - PATCH requests93- `delete_*` - DELETE requests94- `purge_*` - Permanent deletion9596**See:**97- `client/func/base.py` - BaseFunction and @api_function98- `client/func/fair_share.py` - Complete implementation example99100### Pydantic Models (Future Standard)101102**Current state:**103- **attrs** - Current usage in `client/cli/types.py`104- **Pydantic** - Future standard in `common/dto/`105- **New code should use Pydantic**106107**Why Pydantic:**108- Runtime type validation109- Automatic JSON serialization/deserialization110- IDE autocomplete and type checking111- Better error messages112113**Usage:**114- `model_dump(mode="json", exclude_none=True)` - To JSON dict115- `model_validate(data)` - From JSON dict116- Handles nested models automatically117118**See:**119- `client/func/fair_share.py` - Pydantic usage in SDK120- `common/dto/fair_share.py` - Shared DTO definitions121122### SDK Implementation Flow123124**Standard pattern:**1251. Define request/response Pydantic models1262. Create method with @api_function decorator1273. Build Request object with endpoint and data1284. Fetch JSON response from session1295. Parse with `Response.model_validate(data)`1306. Return typed result131132**See complete example:**133- `client/func/fair_share.py` - All standard operations (create, get, search, update, delete)134135## CLI Implementation Patterns136137### Click Command Structure138139**Command organization:**140- `ExtendedCommandGroup` - Enhanced Click group with interrupt handling141- `LazyGroup` - Defers module imports until needed142- `@pass_ctx_obj` - Type-safe CLIContext injection143144**See:**145- `client/cli/main.py` - Main entry and command groups146- `client/cli/extensions.py` - @pass_ctx_obj decorator147- `client/cli/fair_share/__init__.py` - LazyGroup usage148149### Output Handlers150151**FieldSpec:**152- Defines displayable fields for a model153- Specifies field name, human-readable label, formatters154- Supports nested fields (dot notation) and custom formatters155156**BaseOutputHandler:**157- `print_item()` - Single item display158- `print_list()` - Collection display159- `print_error()` - Error messages160- Implementations: ConsoleOutputHandler (table), JSONOutputHandler161162**See:**163- `client/output/types.py` - FieldSpec and BaseOutputHandler protocol164- `client/output/fields.py` - Field utilities165- `client/cli/fair_share/commands.py` - FieldSpec definitions and usage166167### CLI + SDK Integration168169**Pattern:**170- Click command → Session context → SDK function → REST API171- Catch `BackendAIError` exceptions172- Use `ctx.output.print_error()` for consistent formatting173- Exit with appropriate ExitCode174175**Error handling:**176- All exceptions inherit from `BackendAIError`177- Exit codes from `ExitCode` enum (OK=0, FAILURE=1, INVALID_ARGUMENT=2, PERMISSION_DENIED=3)178- `ctx.output.print_error()` handles both console and JSON modes179180**See:**181- `client/cli/fair_share/commands.py` - Complete CLI integration182- `client/cli/types.py` - CLIContext and ExitCode183- `client/exceptions.py` - Exception hierarchy184185## Common Patterns186187| Pattern | SDK | CLI |188|---------|-----|-----|189| **Session** | `async with AsyncSession()` | `with Session()` (sync wrapper) |190| **Request** | `Request("POST", "/endpoint", json={...})` | SDK handles internally |191| **Response** | `Response.model_validate(data)` | SDK returns parsed model |192| **Output** | Return Pydantic model | `ctx.output.print_item()` or `print_list()` |193| **Error** | Raise `BackendAIError` subclass | `print_error()` + `sys.exit(ExitCode)` |194| **Config** | `APIConfig()` reads environment | Passed via `CLIContext` |195| **Async** | Always async (`async def`, `await`) | Sync wrapper handles internally |196197## Implementation Checklist198199When implementing new CLI/SDK feature:2002011. ✅ **Implement REST API** (`/api-guide`)202 - Handler with standard operations203 - Proper error responses2042052. ✅ **Define Pydantic models**206 - Request DTOs in `common/dto/`207 - Response DTOs shared with API layer2082093. ✅ **Implement SDK function**210 - Add method to appropriate function group211 - Use `@api_function` decorator212 - Handle all operations (create, get, search, update, delete)2132144. ✅ **Define FieldSpec**215 - List displayable fields216 - Add formatters for complex types217 - Consider both console and JSON output2182195. ✅ **Implement CLI commands**220 - Add Click command group221 - Map options/arguments to SDK calls222 - Handle errors with proper exit codes2232246. ✅ **Write tests** (`/test-guide`)225 - SDK: Mock HTTP responses (pytest-aiohttp)226 - CLI: Use CliRunner, mock SDK functions (not HTTP)227 - Verify request/response serialization2282297. ✅ **Update documentation**230 - Add usage examples to README231 - Document new CLI commands232233## Testing234235**SDK tests:**236- Mock HTTP responses with `pytest-aiohttp`237- Test request serialization and response parsing238- Verify Pydantic model validation239240**CLI tests:**241- Use Click's `CliRunner` for command invocation242- Mock SDK functions (not HTTP layer)243- Test output formatting and exit codes244245**See:**246- `/test-guide` skill for testing workflow247- `tests/unit/client/` - Test examples248249## Reference Files250251**Complete implementations:**252- `client/func/fair_share.py` - Full SDK with Pydantic models253- `client/cli/fair_share/commands.py` - Full CLI with all patterns254255**Architecture components:**256- `client/session.py` - Session and AsyncSession257- `client/func/base.py` - @api_function and BaseFunction258- `client/cli/main.py` - CLI entry point259- `client/cli/extensions.py` - @pass_ctx_obj decorator260- `client/output/types.py` - FieldSpec and output handlers261- `client/config.py` - APIConfig262- `client/cli/types.py` - CLIContext and ExitCode263- `client/exceptions.py` - Exception hierarchy264265## V2 SDK/CLI (New Pattern)266267**All new SDK/CLI work MUST use the v2 pattern.**268269### V2 SDK Client (`client/v2/domains_v2/`)270271- Inherits `BaseDomainClient`, receives `BackendAIAuthClient` via constructor272- Uses v2 DTOs from `common/dto/manager/v2/` exclusively273- `typed_request()` handles serialization and response parsing274- Registry: `V2ClientRegistry` (`client/v2/v2_registry.py`) with `@cached_property` per domain275276### V2 CLI (`client/cli/v2/`)277278**Command pattern:** `./bai [admin] {entity} [{sub-entity}] {operation}`279280- `admin_` SDK methods → commands under `./bai admin {entity} ...`281- Non-admin methods → `./bai {entity} ...`282- Entity names are **singular** (domain, user, agent)283- Each entity has its own directory with `__init__.py` + `commands.py`284- Admin commands in `admin/{entity}.py`285- Sub-entities as separate Click sub-groups (e.g., revision, channel, role)286- Construct Pydantic DTOs with **explicit field arguments** (never kwargs unpacking)287- Filter options are domain-specific CLI args (`--name-contains`, `--status`, etc.)288- `--order-by field:direction` supports multiple values289- Config from `~/.backend.ai/` via `load_v2_config()` → `V2ConnectionConfig` dataclass290291### V2 Config (`~/.backend.ai/`)292293- `config.toml` — endpoint, endpoint_type, api_version294- `credentials.toml` — access_key, secret_key295- `session/cookie.dat` — webserver session cookie (login/logout)296297**Related skills:**298- `/api-guide` - REST API implementation (prerequisite)299- `/test-guide` - Testing workflow300- `/local-dev` - Service management and CLI testing