Skip to Content
FeaturesModulesCentrifugo WebSocketClient Generation

Client Code Generation

Automatically generate type-safe clients for Python, TypeScript, Go, and Swift 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 ✓ Generated Swift client Successfully generated 4 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 # Only Swift (iOS/macOS) python manage.py generate_centrifugo_clients --output ./opensdk --swift # 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!

Swift Client (iOS/macOS)

opensdk/swift/ ├── Sources/CentrifugoClient/ │ ├── Types.swift # Codable structs │ ├── RPCClient.swift # Actor-based RPC client │ ├── APIClient.swift # Typed API wrapper │ └── AnyCodable.swift # Dynamic JSON support ├── Package.swift # Swift Package Manager manifest ├── README.md # Usage docs └── CLAUDE.md # AI documentation

Requirements:

  • Swift 5.9+
  • iOS 13+ / macOS 10.15+
  • SwiftCentrifuge library (optional, for full implementation)

Key Features:

  • ✅ Swift Concurrency (async/await)
  • ✅ Actor-based thread safety
  • ✅ Full Codable + Sendable conformance
  • ✅ Automatic snake_casecamelCase mapping
  • ✅ Fire-and-forget method support

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

Python → Swift

Python TypeSwift Type
strString
intInt
floatDouble
boolBool
List[T][T]
Dict[str, T][String: T]
Optional[T]T?
BaseModelstruct: Codable, Sendable
AnyAnyCodable

Naming Conventions

Method Names

RPC method tasks.get_stats generates:

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

Model Names

Python model TaskStatsParams generates:

  • Python: TaskStatsParams (unchanged)
  • TypeScript: TaskStatsParams (interface)
  • Go: TaskStatsParams (struct)
  • Swift: TaskStatsParams (struct: Codable, Sendable)

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 }

Swift Client (APIClient.swift):

public actor CentrifugoAPIClient { private let rpc: CentrifugoRPCClient public init(rpc: CentrifugoRPCClient) { self.rpc = rpc } /// Get task statistics for a user. public func tasksGetStats(params: TaskStatsParams) async throws -> TaskStatsResult { return try await rpc.call( method: "tasks.get_stats", params: params ) } }

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

Swift: Modern Concurrency

Swift clients use modern Swift concurrency features:

// Actor-based API client (thread-safe) let api = CentrifugoAPIClient(rpc: rpcClient) // Async/await with automatic type inference let result = try await api.tasksGetStats(params: params) print(result.total) // ✅ Type-safe print(result.completed) // ✅ Type-safe // Fire-and-forget methods (no return value) try await api.terminalInput(params: inputParams) // Returns Void

CodingKeys Auto-Generation:

public struct TaskStatsParams: Codable, Sendable { public var userId: String // camelCase in Swift public var includeCompleted: Bool enum CodingKeys: String, CodingKey { case userId = "user_id" // snake_case in JSON case includeCompleted = "include_completed" } }

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

Swift

cd opensdk/swift swift build # Build package swift test # Run tests (if any) xcodebuild -scheme CentrifugoClient build # Xcode build

Or add as local package in Xcode:

  1. File → Add Package Dependencies
  2. Choose “Add Local…”
  3. Select opensdk/swift folder

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: Test Swift Client run: | cd opensdk/swift swift build - 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.modules.django_centrifugo.router import get_global_router router = get_global_router() print(router.list_methods())

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

Swift Build Errors

Problem: Cannot find type 'AnyCodable' in scope

Solution: Make sure all generated files are included:

ls opensdk/swift/Sources/CentrifugoClient/ # Should include: AnyCodable.swift, Types.swift, APIClient.swift, RPCClient.swift

Problem: Type 'X' does not conform to protocol 'Sendable'

Solution: All generated types are Sendable. If you’re extending them, ensure your extensions are also Sendable.

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
Last updated on