Skip to main content

Client Code Generation

Automatically generate type-safe clients for Python, TypeScript, and Go from your RPC handlers.

Quick Start

Generate All Clients

# From your Django project root
python manage.py generate_centrifugo_clients --output ./opensdk --all

Output:

Found 10 RPC methods
- tasks.list: ListTasksParams -> TaskListResult
- tasks.create: CreateTaskParams -> TaskResult
- tasks.update: UpdateTaskParams -> TaskResult
- tasks.delete: DeleteTaskParams -> SuccessResult
...

✓ Generated Python client
✓ Generated TypeScript client
✓ Generated Go client

Successfully generated 3 client(s)

Generate Specific Languages

# Only Python
python manage.py generate_centrifugo_clients --output ./opensdk --python

# Only TypeScript
python manage.py generate_centrifugo_clients --output ./opensdk --typescript

# Only Go
python manage.py generate_centrifugo_clients --output ./opensdk --go

# Python + TypeScript
python manage.py generate_centrifugo_clients --output ./opensdk --python --typescript

Integration with Workflow

Using make api

The recommended way is to use the integrated make api command:

make api

This automatically:

  1. Generates OpenAPI clients (HTTP REST)
  2. Generates Centrifugo clients (WebSocket RPC)
  3. Copies clients to frontend packages
  4. Builds @api package

Manual Integration

Add to your generate_api.py:

# core/management/commands/generate_api.py
from django.core.management import call_command

def handle(self, *args, **options):
# Step 1: OpenAPI clients
call_command('generate_clients')

# Step 2: Centrifugo clients
call_command('generate_centrifugo_clients',
output='./opensdk',
all=True)

# Step 3: Copy to frontend
...

Generated File Structure

Python Client

opensdk/python/
├── __init__.py # Exports
├── models.py # Pydantic models (generated)
├── rpc_client.py # Base RPC client
├── client.py # Typed API wrapper
├── requirements.txt # centrifuge, pydantic
└── README.md # Usage docs

Dependencies:

  • centrifuge - Official Centrifugo Python client
  • pydantic>=2.0 - Type validation

TypeScript Client

opensdk/typescript/
├── index.ts # Exports
├── types.ts # TypeScript interfaces
├── rpc-client.ts # Base RPC client
├── client.ts # Typed API wrapper
├── package.json # centrifuge dependency
├── tsconfig.json # TS config
└── README.md # Usage docs

Dependencies:

  • centrifuge - Official Centrifugo JS client

Go Client

opensdk/go/
├── types.go # Go structs
├── rpc_client.go # Base RPC client
├── client.go # Typed API wrapper
├── go.mod # nhooyr.io/websocket v1.8.10
└── README.md # Usage docs

Dependencies:

  • nhooyr.io/websocket v1.8.10 - NO GitHub dependencies!

Type Conversion

Python → TypeScript

Python TypeTypeScript Type
strstring
int, floatnumber
boolboolean
List[T]T[]
Dict[str, T]{ [key: string]: T }
Optional[T]T | null
BaseModelinterface

Python → Go

Python TypeGo Type
strstring
intint64
floatfloat64
boolbool
List[T][]T
Dict[str, T]map[string]T
Optional[T]*T
BaseModelstruct

Naming Conventions

Method Names

RPC method tasks.get_stats generates:

  • Python: api.tasks_get_stats(...)
  • TypeScript: api.tasksGetStats(...)
  • Go: api.TasksGetStats(ctx, ...)

Model Names

Python model TaskStatsParams generates:

  • Python: TaskStatsParams (unchanged)
  • TypeScript: TaskStatsParams (interface)
  • Go: TaskStatsParams (struct)

Generated Client Architecture

Two-Layer Design

Layer 1: Base RPC Client

  • Handles WebSocket connection
  • Implements correlation ID pattern
  • Publishes to rpc.requests
  • Subscribes to user#{user_id}
  • Matches responses by correlation_id

Layer 2: Typed API Client

  • Thin wrapper over base client
  • One type-safe method per RPC endpoint
  • Automatic serialization/deserialization
  • Full type checking

Example Generated Code

Python Client (client.py):

class APIClient:
def __init__(self, rpc_client: CentrifugoRPCClient):
self.rpc = rpc_client

async def tasks_get_stats(
self,
params: TaskStatsParams
) -> TaskStatsResult:
"""Get task statistics for a user."""
result = await self.rpc.call("tasks.get_stats", params.model_dump())
return TaskStatsResult(**result)

TypeScript Client (client.ts):

export class APIClient {
constructor(private rpc: CentrifugoRPCClient) {}

async tasksGetStats(params: TaskStatsParams): Promise<TaskStatsResult> {
const result = await this.rpc.call('tasks.get_stats', params);
return result as TaskStatsResult;
}
}

Go Client (client.go):

func (api *APIClient) TasksGetStats(
ctx context.Context,
params TaskStatsParams,
) (*TaskStatsResult, error) {
result, err := api.rpc.Call(ctx, "tasks.get_stats", params)
if err != nil {
return nil, err
}

var response TaskStatsResult
if err := json.Unmarshal(result, &response); err != nil {
return nil, err
}

return &response, nil
}

Special Features

Go Client: No GitHub Dependencies

The Go client uses nhooyr.io/websocket instead of GitHub-hosted libraries:

Why?

  • ✅ Better for enterprise proxies
  • ✅ Air-gapped environment support
  • ✅ Clean module path
  • ✅ Minimal dependencies

go.mod:

module example.com/centrifugo_client

require (
nhooyr.io/websocket v1.8.10
)

UUID Generation: Uses crypto/rand from stdlib (no external UUID library):

func generateUUID() string {
b := make([]byte, 16)
rand.Read(b)
b[6] = (b[6] & 0x0f) | 0x40 // Version 4
b[8] = (b[8] & 0x3f) | 0x80 // Variant is 10
return fmt.Sprintf("%x-%x-%x-%x-%x",
b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

TypeScript: Full Type Safety

Generated TypeScript clients have complete type safety:

// Type checking at compile time
const params: TaskStatsParams = {
user_id: "123",
include_completed: true
};

const result: TaskStatsResult = await api.tasksGetStats(params);
// ^-- TypeScript knows the exact type

console.log(result.total); // ✅ OK
console.log(result.completed); // ✅ OK
console.log(result.invalid); // ❌ Compile error

Python: Runtime Validation

Python clients use Pydantic for runtime validation:

# Valid params
params = TaskStatsParams(user_id="123", include_completed=True)
result = await api.tasks_get_stats(params)

# Invalid params - raises ValidationError
params = TaskStatsParams(user_id=123) # ❌ Wrong type

Testing Generated Clients

Python

cd opensdk/python
pip install -r requirements.txt
python -c "from client import APIClient; print('✓ Import OK')"

TypeScript

cd opensdk/typescript
npm install
npx tsc --noEmit # Check types

Go

cd opensdk/go
go mod tidy
go build . # Compile
go vet . # Static analysis

Continuous Integration

GitHub Actions

name: Generate Clients

on:
push:
paths:
- 'core/centrifugo_handlers.py'

jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.11'

- name: Generate Clients
run: |
python manage.py generate_centrifugo_clients --output ./opensdk --all

- name: Test Go Client
run: |
cd opensdk/go
go build .

- name: Test TS Client
run: |
cd opensdk/typescript
npm install
npx tsc --noEmit

- name: Commit Generated Clients
run: |
git add opensdk/
git commit -m "chore: regenerate clients"
git push

Troubleshooting

No Handlers Found

Problem: Found 0 RPC methods

Solution:

  1. Verify handlers use @websocket_rpc decorator
  2. Check handlers are imported in apps.py
  3. Run Django shell:
    from django_cfg.apps.centrifugo.router import get_message_router
    router = get_message_router()
    print(router._handlers.keys())

Go Build Fails

Problem: package nhooyr.io/websocket is not in GOROOT

Solution:

cd opensdk/go
go mod tidy # Download dependencies

TypeScript Type Errors

Problem: Property 'foo' does not exist

Solution: Regenerate clients after model changes:

python manage.py generate_centrifugo_clients --output ./opensdk --typescript

Next Steps


Generation Best Practices
  • ✅ Regenerate after handler changes
  • ✅ Commit generated clients to git
  • ✅ Test clients before deployment
  • ✅ Version generated clients with your app
  • ✅ Use CI/CD to auto-generate