or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdrelay.mdschema-execution.mdtype-system.md

relay.mddocs/

0

# Relay Integration

1

2

Graphene provides complete GraphQL Relay specification support, enabling standardized patterns for global object identification, cursor-based pagination, and mutations with client IDs. This includes the Node interface for globally unique objects, connection-based pagination for efficient data fetching, and mutation patterns that support optimistic updates.

3

4

## Capabilities

5

6

### Node Interface and Global IDs

7

8

Implements the Relay Node interface for global object identification across your GraphQL schema.

9

10

```python { .api }

11

class Node(Interface):

12

"""

13

Relay Node interface implementation with global ID support.

14

15

Features:

16

- Automatic ID field injection

17

- Global ID encoding/decoding

18

- Type validation for node objects

19

- Integration with Relay client libraries

20

21

Class Methods:

22

Field(*args, **kwargs): Creates NodeField for single node queries

23

get_node_from_global_id(info, global_id, only_type=None): Node retrieval

24

to_global_id(type_, id): Global ID creation

25

resolve_global_id(info, global_id): Global ID parsing

26

27

Usage:

28

class User(graphene.ObjectType):

29

class Meta:

30

interfaces = (Node,)

31

32

name = graphene.String()

33

email = graphene.String()

34

35

@classmethod

36

def get_node(cls, info, id):

37

return get_user_by_id(id)

38

"""

39

40

class GlobalID(Field):

41

"""

42

Automatic global ID field generation for Node types.

43

44

Parameters:

45

node: Node type this ID represents

46

parent_type: Parent type for ID resolution

47

required: Whether field is NonNull

48

global_id_type: Custom GlobalIDType implementation

49

50

Features:

51

- Automatic ID resolution and encoding

52

- Configurable ID type implementations

53

- Integration with Relay pagination

54

55

Usage:

56

class User(graphene.ObjectType):

57

class Meta:

58

interfaces = (Node,)

59

60

id = GlobalID()

61

name = graphene.String()

62

"""

63

64

def is_node(objecttype):

65

"""

66

Check if ObjectType implements Node interface.

67

68

Args:

69

objecttype: GraphQL ObjectType to check

70

71

Returns:

72

bool: True if type implements Node interface

73

74

Usage:

75

if graphene.is_node(User):

76

print("User implements Node interface")

77

"""

78

```

79

80

### Global ID Type System

81

82

Flexible global ID implementations supporting different encoding strategies.

83

84

```python { .api }

85

class BaseGlobalIDType:

86

"""

87

Base class for global ID type implementations.

88

89

Attributes:

90

graphene_type: GraphQL scalar type used (default: ID)

91

92

Abstract Methods:

93

resolve_global_id(global_id): Parse global ID to (type, id) tuple

94

to_global_id(type_, id): Encode type and ID to global ID string

95

96

Usage:

97

class CustomGlobalIDType(BaseGlobalIDType):

98

def resolve_global_id(self, global_id):

99

parts = global_id.split('.')

100

return (parts[0], parts[1])

101

102

def to_global_id(self, type_, id):

103

return f"{type_}.{id}"

104

"""

105

106

class DefaultGlobalIDType(BaseGlobalIDType):

107

"""

108

Base64-encoded "TypeName:id" format (Relay standard).

109

110

Features:

111

- Standard Relay specification compliance

112

- Base64 encoding for opacity

113

- Type safety with parsing validation

114

- Compatible with Relay client libraries

115

116

Methods:

117

Uses graphql-relay's to_global_id/from_global_id functions

118

119

Format: base64("TypeName:id")

120

Example: "VXNlcjox" -> "User:1"

121

"""

122

123

class SimpleGlobalIDType(BaseGlobalIDType):

124

"""

125

Pass-through ID type that uses raw ID values.

126

127

Warning: User responsible for ensuring global uniqueness

128

Features:

129

- No encoding/decoding overhead

130

- Direct ID passthrough

131

- Requires globally unique IDs

132

133

Use Case: When IDs are already globally unique (UUIDs, etc.)

134

"""

135

136

class UUIDGlobalIDType(BaseGlobalIDType):

137

"""

138

UUID-based global IDs with native UUID support.

139

140

Features:

141

- Uses UUID GraphQL type instead of ID

142

- Inherently global uniqueness

143

- No type prefix needed

144

- Automatic UUID validation

145

146

Attributes:

147

graphene_type: UUID (instead of ID)

148

"""

149

```

150

151

### Connection System

152

153

Cursor-based pagination following the Relay Connection specification.

154

155

```python { .api }

156

class Connection(ObjectType):

157

"""

158

Relay cursor-based pagination container.

159

160

Features:

161

- Automatic Edge class generation

162

- PageInfo integration for pagination metadata

163

- Configurable strict typing

164

- Cursor-based navigation

165

166

Meta Options:

167

node: Required ObjectType that this connection contains

168

name: Custom connection name (defaults to {Node}Connection)

169

strict_types: Enforce strict edge/connection typing

170

171

Generated Fields:

172

edges: List of Edge objects containing nodes and cursors

173

page_info: PageInfo object with pagination metadata

174

175

Usage:

176

class UserConnection(Connection):

177

class Meta:

178

node = User

179

180

# Automatically generates:

181

# - UserEdge with node and cursor fields

182

# - edges field returning [UserEdge]

183

# - page_info field returning PageInfo

184

"""

185

186

class ConnectionField(Field):

187

"""

188

Field that automatically handles Relay-style pagination.

189

190

Features:

191

- Automatic Relay pagination arguments (before, after, first, last)

192

- Iterable to connection conversion

193

- Resolver wrapping for pagination

194

- Integration with Connection types

195

196

Auto Arguments:

197

before: Cursor for backward pagination

198

after: Cursor for forward pagination

199

first: Number of items from start

200

last: Number of items from end

201

202

Methods:

203

resolve_connection(): Converts iterables to connections

204

connection_resolver(): Wraps resolvers for pagination

205

206

Usage:

207

class Query(graphene.ObjectType):

208

users = ConnectionField(UserConnection)

209

210

def resolve_users(self, info, **args):

211

# Return iterable - automatically converted to connection

212

return User.objects.all()

213

"""

214

215

class PageInfo(ObjectType):

216

"""

217

Relay pagination metadata following the specification.

218

219

Fields:

220

has_next_page: Boolean indicating if more items exist forward

221

has_previous_page: Boolean indicating if more items exist backward

222

start_cursor: Cursor of first item in current page

223

end_cursor: Cursor of last item in current page

224

225

Features:

226

- Relay specification compliant

227

- Automatic cursor generation

228

- Pagination state management

229

- Client navigation support

230

231

Usage:

232

# Automatically included in Connection types

233

# Used by Relay clients for pagination UI

234

query {

235

users(first: 10) {

236

edges {

237

node {

238

name

239

}

240

cursor

241

}

242

pageInfo {

243

hasNextPage

244

hasPreviousPage

245

startCursor

246

endCursor

247

}

248

}

249

}

250

"""

251

```

252

253

### Relay Mutations

254

255

Standardized mutation patterns with client IDs for optimistic updates.

256

257

```python { .api }

258

class ClientIDMutation(Mutation):

259

"""

260

Relay-style mutations with client mutation ID support.

261

262

Features:

263

- Automatic Input class generation

264

- client_mutation_id field injection

265

- Payload naming convention ({MutationName}Payload)

266

- Support for optimistic updates

267

268

Required Method:

269

mutate_and_get_payload(): Implementation method with client ID

270

271

Generated Features:

272

- Input class with client_mutation_id field

273

- Payload class with client_mutation_id field

274

- Automatic argument extraction

275

276

Usage:

277

class CreateUser(ClientIDMutation):

278

class Input:

279

name = graphene.String(required=True)

280

email = graphene.String()

281

282

user = graphene.Field(User)

283

284

@classmethod

285

def mutate_and_get_payload(cls, root, info, **input):

286

# client_mutation_id automatically handled

287

user = create_user(

288

name=input['name'],

289

email=input.get('email')

290

)

291

return CreateUser(user=user)

292

293

# Generates mutation field that accepts:

294

# input {

295

# name: String!

296

# email: String

297

# clientMutationId: String

298

# }

299

#

300

# Returns payload:

301

# {

302

# user: User

303

# clientMutationId: String

304

# }

305

"""

306

```

307

308

## Usage Examples

309

310

### Implementing Node Interface

311

312

```python

313

import graphene

314

from graphene import relay

315

316

class User(graphene.ObjectType):

317

class Meta:

318

interfaces = (relay.Node,)

319

320

name = graphene.String()

321

email = graphene.String()

322

posts = relay.ConnectionField('PostConnection')

323

324

@classmethod

325

def get_node(cls, info, id):

326

"""Required method for Node interface."""

327

return get_user_by_id(id)

328

329

def resolve_posts(self, info, **args):

330

return get_posts_for_user(self.id)

331

332

class Post(graphene.ObjectType):

333

class Meta:

334

interfaces = (relay.Node,)

335

336

title = graphene.String()

337

content = graphene.String()

338

author = graphene.Field(User)

339

340

@classmethod

341

def get_node(cls, info, id):

342

return get_post_by_id(id)

343

344

# Connection classes

345

class PostConnection(relay.Connection):

346

class Meta:

347

node = Post

348

349

class UserConnection(relay.Connection):

350

class Meta:

351

node = User

352

353

# Query with Node field

354

class Query(graphene.ObjectType):

355

node = relay.Node.Field()

356

users = relay.ConnectionField(UserConnection)

357

posts = relay.ConnectionField(PostConnection)

358

359

def resolve_users(self, info, **args):

360

return User.objects.all()

361

362

def resolve_posts(self, info, **args):

363

return Post.objects.all()

364

```

365

366

### Custom Global ID Types

367

368

```python

369

import graphene

370

from graphene import relay

371

import uuid

372

373

class UUIDNode(relay.Node):

374

"""Node interface using UUID global IDs."""

375

376

class Meta:

377

# Use UUID-based global ID type

378

global_id_type = relay.UUIDGlobalIDType()

379

380

class User(graphene.ObjectType):

381

class Meta:

382

interfaces = (UUIDNode,)

383

384

# ID field will use UUID type instead of ID

385

name = graphene.String()

386

387

@classmethod

388

def get_node(cls, info, id):

389

# id is already a UUID object

390

return get_user_by_uuid(id)

391

392

# Custom global ID type

393

class PrefixedGlobalIDType(relay.BaseGlobalIDType):

394

"""Custom global ID with prefix."""

395

396

def to_global_id(self, type_, id):

397

return f"app_{type_}_{id}"

398

399

def resolve_global_id(self, global_id):

400

parts = global_id.split('_')

401

if len(parts) >= 3 and parts[0] == 'app':

402

return (parts[1], '_'.join(parts[2:]))

403

raise ValueError(f"Invalid global ID format: {global_id}")

404

405

class CustomNode(relay.Node):

406

class Meta:

407

global_id_type = PrefixedGlobalIDType()

408

```

409

410

### Relay Mutations with Input Validation

411

412

```python

413

import graphene

414

from graphene import relay

415

416

class CreateUserInput(graphene.InputObjectType):

417

name = graphene.String(required=True)

418

email = graphene.String(required=True)

419

age = graphene.Int()

420

421

class CreateUser(relay.ClientIDMutation):

422

class Input:

423

data = graphene.Field(CreateUserInput, required=True)

424

425

user = graphene.Field(User)

426

success = graphene.Boolean()

427

errors = graphene.List(graphene.String)

428

429

@classmethod

430

def mutate_and_get_payload(cls, root, info, data, client_mutation_id=None):

431

errors = []

432

433

# Validation

434

if len(data.name) < 2:

435

errors.append("Name must be at least 2 characters")

436

437

if '@' not in data.email:

438

errors.append("Invalid email format")

439

440

if errors:

441

return CreateUser(success=False, errors=errors)

442

443

# Create user

444

user = create_user(

445

name=data.name,

446

email=data.email,

447

age=data.age

448

)

449

450

return CreateUser(

451

user=user,

452

success=True,

453

errors=[]

454

)

455

456

class UpdateUser(relay.ClientIDMutation):

457

class Input:

458

id = graphene.ID(required=True)

459

name = graphene.String()

460

email = graphene.String()

461

462

user = graphene.Field(User)

463

464

@classmethod

465

def mutate_and_get_payload(cls, root, info, id, **input):

466

# Resolve node from global ID

467

node_type, node_id = relay.Node.resolve_global_id(info, id)

468

469

if node_type != 'User':

470

raise Exception('Invalid user ID')

471

472

user = get_user_by_id(node_id)

473

if not user:

474

raise Exception('User not found')

475

476

# Update fields

477

for field, value in input.items():

478

if value is not None:

479

setattr(user, field, value)

480

481

save_user(user)

482

return UpdateUser(user=user)

483

484

class Mutation(graphene.ObjectType):

485

create_user = CreateUser.Field()

486

update_user = UpdateUser.Field()

487

```

488

489

### Advanced Connection Filtering

490

491

```python

492

import graphene

493

from graphene import relay

494

495

class UserConnection(relay.Connection):

496

class Meta:

497

node = User

498

499

total_count = graphene.Int()

500

501

def resolve_total_count(self, info):

502

return len(self.iterable)

503

504

class Query(graphene.ObjectType):

505

users = relay.ConnectionField(

506

UserConnection,

507

# Custom arguments for filtering

508

name_contains=graphene.String(),

509

active_only=graphene.Boolean(),

510

sort_by=graphene.String()

511

)

512

513

def resolve_users(self, info, name_contains=None, active_only=None, sort_by=None, **args):

514

queryset = User.objects.all()

515

516

# Apply filters

517

if name_contains:

518

queryset = queryset.filter(name__icontains=name_contains)

519

520

if active_only:

521

queryset = queryset.filter(is_active=True)

522

523

# Apply sorting

524

if sort_by == 'name':

525

queryset = queryset.order_by('name')

526

elif sort_by == 'created':

527

queryset = queryset.order_by('-created_at')

528

529

return queryset

530

531

# Usage in GraphQL query:

532

# query {

533

# users(first: 10, nameContains: "john", activeOnly: true, sortBy: "name") {

534

# edges {

535

# node {

536

# name

537

# email

538

# }

539

# cursor

540

# }

541

# pageInfo {

542

# hasNextPage

543

# endCursor

544

# }

545

# totalCount

546

# }

547

# }

548

```