or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-types.mdexperimental.mdextensions.mdfederation.mdfields-resolvers.mdframework-integrations.mdindex.mdrelay.mdschema-execution.mdutilities.md

utilities.mddocs/

0

# Data Loading and Utilities

1

2

Utilities for efficient data loading, file uploads, type conversions, and common GraphQL patterns. These utilities help solve common problems in GraphQL API development like the N+1 query problem and provide helpful tools for schema manipulation.

3

4

## Capabilities

5

6

### DataLoader

7

8

Batch loading utility for efficient data fetching and N+1 query problem prevention.

9

10

```python { .api }

11

class DataLoader:

12

"""Batch loading utility for efficient data fetching."""

13

14

def __init__(

15

self,

16

load_fn: Callable[[List[Any]], Awaitable[List[Any]]],

17

*,

18

batch: bool = True,

19

max_batch_size: int = None,

20

batch_scheduler: Callable = None,

21

cache: bool = True,

22

cache_key_fn: Callable[[Any], Any] = None,

23

cache_map: AbstractCache = None

24

):

25

"""

26

Initialize DataLoader.

27

28

Args:

29

load_fn: Async function that takes list of keys and returns list of values

30

batch: Whether to batch requests

31

max_batch_size: Maximum size of batches

32

batch_scheduler: Custom batch scheduling function

33

cache: Whether to cache results

34

cache_key_fn: Function to generate cache keys

35

cache_map: Custom cache implementation

36

"""

37

38

async def load(self, key: Any) -> Any:

39

"""

40

Load single value by key.

41

42

Args:

43

key: Key to load

44

45

Returns:

46

Loaded value

47

"""

48

49

async def load_many(self, keys: List[Any]) -> List[Any]:

50

"""

51

Load multiple values by keys.

52

53

Args:

54

keys: List of keys to load

55

56

Returns:

57

List of loaded values in same order as keys

58

"""

59

60

def clear(self, key: Any) -> "DataLoader":

61

"""

62

Clear cache for specific key.

63

64

Args:

65

key: Key to clear from cache

66

67

Returns:

68

DataLoader instance for chaining

69

"""

70

71

def clear_all(self) -> "DataLoader":

72

"""

73

Clear all cached values.

74

75

Returns:

76

DataLoader instance for chaining

77

"""

78

79

def prime(self, key: Any, value: Any) -> "DataLoader":

80

"""

81

Prime cache with key-value pair.

82

83

Args:

84

key: Key to prime

85

value: Value to cache

86

87

Returns:

88

DataLoader instance for chaining

89

"""

90

```

91

92

**Usage Example:**

93

94

```python

95

import asyncio

96

from strawberry.dataloader import DataLoader

97

98

# Define batch loading function

99

async def load_users_by_ids(user_ids: List[str]) -> List[Optional[User]]:

100

"""Load users by IDs from database."""

101

print(f"Batch loading users: {user_ids}") # This should only print once per batch

102

103

# Simulate database query

104

users_data = await database.fetch_users_by_ids(user_ids)

105

106

# Create lookup dictionary

107

user_lookup = {str(user.id): user for user in users_data}

108

109

# Return users in same order as requested IDs

110

return [user_lookup.get(user_id) for user_id in user_ids]

111

112

# Create DataLoader

113

user_loader = DataLoader(load_users_by_ids)

114

115

# Usage in resolvers

116

@strawberry.type

117

class Post:

118

id: strawberry.ID

119

title: str

120

author_id: str

121

122

@strawberry.field

123

async def author(self, info: strawberry.Info) -> Optional[User]:

124

# DataLoader automatically batches these requests

125

return await info.context.user_loader.load(self.author_id)

126

127

@strawberry.type

128

class Query:

129

@strawberry.field

130

async def posts(self, info: strawberry.Info) -> List[Post]:

131

posts = await get_all_posts()

132

# Even though we're loading author for each post,

133

# DataLoader will batch all author requests into a single query

134

return posts

135

136

# Context setup

137

class Context:

138

def __init__(self):

139

self.user_loader = DataLoader(load_users_by_ids)

140

141

async def get_context():

142

return Context()

143

```

144

145

### DataLoader Cache Interface

146

147

Abstract cache interface for custom cache implementations.

148

149

```python { .api }

150

class AbstractCache:

151

"""Abstract interface for DataLoader cache implementations."""

152

153

def get(self, key: Any) -> Any:

154

"""Get value from cache."""

155

156

def set(self, key: Any, value: Any) -> None:

157

"""Set value in cache."""

158

159

def delete(self, key: Any) -> None:

160

"""Delete value from cache."""

161

162

def clear(self) -> None:

163

"""Clear all cache values."""

164

```

165

166

**Custom Cache Example:**

167

168

```python

169

import redis

170

from strawberry.dataloader import AbstractCache

171

172

class RedisCache(AbstractCache):

173

def __init__(self, redis_client: redis.Redis, ttl: int = 300):

174

self.redis = redis_client

175

self.ttl = ttl

176

177

def get(self, key):

178

cached = self.redis.get(f"dataloader:{key}")

179

return pickle.loads(cached) if cached else None

180

181

def set(self, key, value):

182

self.redis.setex(

183

f"dataloader:{key}",

184

self.ttl,

185

pickle.dumps(value)

186

)

187

188

def delete(self, key):

189

self.redis.delete(f"dataloader:{key}")

190

191

def clear(self):

192

for key in self.redis.scan_iter(match="dataloader:*"):

193

self.redis.delete(key)

194

195

# Use custom cache

196

redis_client = redis.Redis()

197

user_loader = DataLoader(

198

load_users_by_ids,

199

cache_map=RedisCache(redis_client, ttl=600)

200

)

201

```

202

203

### DataLoader Internal Types

204

205

Internal types used by DataLoader implementation.

206

207

```python { .api }

208

class LoaderTask:

209

"""Internal task representation for DataLoader."""

210

pass

211

212

class Batch:

213

"""Internal batch representation for DataLoader."""

214

pass

215

```

216

217

### Advanced DataLoader Patterns

218

219

```python

220

# DataLoader with custom cache key function

221

def user_cache_key(user_id: str) -> str:

222

return f"user:{user_id}"

223

224

user_loader = DataLoader(

225

load_users_by_ids,

226

cache_key_fn=user_cache_key

227

)

228

229

# DataLoader with limited batch size

230

large_data_loader = DataLoader(

231

load_large_data,

232

max_batch_size=50 # Limit batch size for large operations

233

)

234

235

# DataLoader without caching (for frequently changing data)

236

realtime_loader = DataLoader(

237

load_realtime_data,

238

cache=False

239

)

240

241

# Priming DataLoader cache

242

async def prime_user_cache(users: List[User]):

243

for user in users:

244

user_loader.prime(user.id, user)

245

246

# DataLoader composition

247

class UserService:

248

def __init__(self):

249

self.user_loader = DataLoader(self._load_users)

250

self.user_profile_loader = DataLoader(self._load_profiles)

251

252

async def _load_users(self, user_ids: List[str]) -> List[User]:

253

return await database.get_users_by_ids(user_ids)

254

255

async def _load_profiles(self, user_ids: List[str]) -> List[UserProfile]:

256

return await database.get_profiles_by_user_ids(user_ids)

257

258

async def get_user_with_profile(self, user_id: str) -> UserWithProfile:

259

user, profile = await asyncio.gather(

260

self.user_loader.load(user_id),

261

self.user_profile_loader.load(user_id)

262

)

263

return UserWithProfile(user=user, profile=profile)

264

```

265

266

## File Uploads

267

268

File upload support for GraphQL mutations with multipart/form-data requests.

269

270

```python { .api }

271

class Upload:

272

"""File upload scalar type for multipart requests."""

273

274

filename: str # Original filename

275

content_type: str # MIME content type

276

277

def read(self, size: int = -1) -> bytes:

278

"""Read file content."""

279

280

async def read_async(self, size: int = -1) -> bytes:

281

"""Read file content asynchronously."""

282

283

def seek(self, offset: int) -> None:

284

"""Seek to position in file."""

285

286

def close(self) -> None:

287

"""Close file handle."""

288

```

289

290

**Usage Example:**

291

292

```python

293

from strawberry.file_uploads import Upload

294

295

@strawberry.type

296

class UploadResult:

297

success: bool

298

filename: str

299

size: int

300

url: Optional[str]

301

302

@strawberry.type

303

class Mutation:

304

@strawberry.mutation

305

async def upload_file(

306

self,

307

file: Upload,

308

description: str = ""

309

) -> UploadResult:

310

# Read file content

311

content = await file.read_async()

312

313

# Save file to storage

314

file_path = f"uploads/{file.filename}"

315

await save_file_to_storage(file_path, content)

316

317

return UploadResult(

318

success=True,

319

filename=file.filename,

320

size=len(content),

321

url=f"/files/{file.filename}"

322

)

323

324

@strawberry.mutation

325

async def upload_multiple_files(

326

self,

327

files: List[Upload]

328

) -> List[UploadResult]:

329

results = []

330

for file in files:

331

content = await file.read_async()

332

file_path = f"uploads/{file.filename}"

333

await save_file_to_storage(file_path, content)

334

335

results.append(UploadResult(

336

success=True,

337

filename=file.filename,

338

size=len(content),

339

url=f"/files/{file.filename}"

340

))

341

342

return results

343

```

344

345

**Frontend Usage (JavaScript):**

346

347

```javascript

348

// Single file upload

349

const mutation = `

350

mutation UploadFile($file: Upload!, $description: String) {

351

uploadFile(file: $file, description: $description) {

352

success

353

filename

354

size

355

url

356

}

357

}

358

`;

359

360

const variables = {

361

file: file, // File object from input element

362

description: "My uploaded file"

363

};

364

365

// Multiple file upload

366

const multiMutation = `

367

mutation UploadMultipleFiles($files: [Upload!]!) {

368

uploadMultipleFiles(files: $files) {

369

success

370

filename

371

size

372

}

373

}

374

`;

375

```

376

377

## Type Creation Utilities

378

379

Utilities for programmatically creating and manipulating GraphQL types.

380

381

```python { .api }

382

def create_type(

383

name: str,

384

fields: Dict[str, Any],

385

*,

386

description: str = None,

387

interfaces: List[Type] = None,

388

directives: List = None

389

) -> Type:

390

"""

391

Programmatically create GraphQL types.

392

393

Args:

394

name: Type name

395

fields: Dictionary of field name to field definition

396

description: Type description

397

interfaces: Interfaces this type implements

398

directives: GraphQL directives to apply

399

400

Returns:

401

New GraphQL type

402

"""

403

404

def merge_types(

405

name: str,

406

types: List[Type],

407

*,

408

description: str = None

409

) -> Type:

410

"""

411

Merge multiple GraphQL types into a single type.

412

413

Args:

414

name: New type name

415

types: List of types to merge

416

description: Merged type description

417

418

Returns:

419

Merged GraphQL type

420

"""

421

```

422

423

**Usage Examples:**

424

425

```python

426

from strawberry.tools import create_type, merge_types

427

428

# Create type programmatically

429

UserType = create_type(

430

"User",

431

{

432

"id": strawberry.ID,

433

"name": str,

434

"email": str,

435

"age": int

436

},

437

description="User account information"

438

)

439

440

# Create type with methods

441

def get_full_name(self) -> str:

442

return f"{self.first_name} {self.last_name}"

443

444

PersonType = create_type(

445

"Person",

446

{

447

"first_name": str,

448

"last_name": str,

449

"full_name": strawberry.field(resolver=get_full_name)

450

}

451

)

452

453

# Merge multiple types

454

@strawberry.type

455

class BaseUser:

456

id: strawberry.ID

457

name: str

458

459

@strawberry.type

460

class UserPreferences:

461

theme: str

462

language: str

463

464

# Merge into single type

465

ExtendedUser = merge_types(

466

"ExtendedUser",

467

[BaseUser, UserPreferences],

468

description="User with preferences"

469

)

470

```

471

472

## Schema Utilities

473

474

### Schema Printing

475

476

Print GraphQL schema in SDL (Schema Definition Language) format.

477

478

```python { .api }

479

def print_schema(schema: Schema) -> str:

480

"""

481

Print GraphQL schema as SDL string.

482

483

Args:

484

schema: GraphQL schema to print

485

486

Returns:

487

Schema Definition Language string

488

"""

489

```

490

491

**Usage Example:**

492

493

```python

494

from strawberry.printer import print_schema

495

496

schema = strawberry.Schema(query=Query, mutation=Mutation)

497

sdl = print_schema(schema)

498

499

print(sdl)

500

# Output:

501

# type Query {

502

# users: [User!]!

503

# user(id: ID!): User

504

# }

505

#

506

# type User {

507

# id: ID!

508

# name: String!

509

# email: String!

510

# }

511

```

512

513

## Subscription Utilities

514

515

Constants and utilities for GraphQL subscriptions.

516

517

```python { .api }

518

# WebSocket protocol constants

519

GRAPHQL_TRANSPORT_WS_PROTOCOL: str # Modern GraphQL-WS transport protocol

520

GRAPHQL_WS_PROTOCOL: str # Legacy GraphQL-WS protocol

521

```

522

523

**Usage Example:**

524

525

```python

526

from strawberry.subscriptions import (

527

GRAPHQL_TRANSPORT_WS_PROTOCOL,

528

GRAPHQL_WS_PROTOCOL

529

)

530

531

# Use in ASGI app

532

app = strawberry.asgi.GraphQL(

533

schema,

534

subscription_protocols=[

535

GRAPHQL_TRANSPORT_WS_PROTOCOL,

536

GRAPHQL_WS_PROTOCOL # For backwards compatibility

537

]

538

)

539

```

540

541

## Code Generation Utilities

542

543

### Schema Code Generation

544

545

Generate Python code from GraphQL queries.

546

547

```python { .api }

548

class CodegenFile:

549

"""Generated code file."""

550

551

path: str

552

content: str

553

554

class CodegenResult:

555

"""Result of code generation."""

556

557

files: List[CodegenFile]

558

559

class QueryCodegen:

560

"""Generate code from GraphQL queries."""

561

562

def __init__(self, schema: Schema): ...

563

564

def generate(

565

self,

566

query: str,

567

*,

568

target_language: str = "python",

569

plugins: List[QueryCodegenPlugin] = None

570

) -> CodegenResult: ...

571

572

class QueryCodegenPlugin:

573

"""Base class for codegen plugins."""

574

pass

575

576

class ConsolePlugin(QueryCodegenPlugin):

577

"""Plugin for console output."""

578

pass

579

```

580

581

### SDL Code Generation

582

583

Generate Strawberry code from GraphQL SDL.

584

585

```python { .api }

586

def codegen(

587

schema_sdl: str,

588

*,

589

output_dir: str = None,

590

plugins: List[str] = None

591

) -> None:

592

"""

593

Generate Strawberry code from GraphQL SDL.

594

595

Args:

596

schema_sdl: GraphQL Schema Definition Language string

597

output_dir: Directory to write generated files

598

plugins: List of codegen plugins to use

599

"""

600

```

601

602

**Usage Example:**

603

604

```python

605

from strawberry.codegen import codegen

606

607

schema_sdl = """

608

type User {

609

id: ID!

610

name: String!

611

email: String!

612

posts: [Post!]!

613

}

614

615

type Post {

616

id: ID!

617

title: String!

618

content: String!

619

author: User!

620

}

621

622

type Query {

623

users: [User!]!

624

posts: [Post!]!

625

}

626

"""

627

628

# Generate Strawberry code from SDL

629

codegen(

630

schema_sdl,

631

output_dir="./generated",

632

plugins=["strawberry"]

633

)

634

```

635

636

## CLI Utilities

637

638

Command-line interface for Strawberry operations.

639

640

```python { .api }

641

def run() -> None:

642

"""Main CLI application entry point."""

643

644

# Available commands:

645

# strawberry server - Development server

646

# strawberry export-schema - Export schema SDL

647

# strawberry codegen - Generate code from SDL

648

# strawberry schema-codegen - Generate types from queries

649

```

650

651

**CLI Usage Examples:**

652

653

```bash

654

# Start development server

655

strawberry server myapp.schema:schema --host 0.0.0.0 --port 8000

656

657

# Export schema to file

658

strawberry export-schema myapp.schema:schema --output schema.graphql

659

660

# Generate code from SDL

661

strawberry codegen --schema schema.graphql --output generated/

662

663

# Generate types from queries

664

strawberry schema-codegen --schema myapp.schema:schema --queries queries/ --output types.py

665

```

666

667

## Performance Utilities

668

669

### Caching Patterns

670

671

```python

672

from functools import lru_cache

673

import asyncio

674

675

class FieldCache:

676

"""Simple field-level caching utility."""

677

678

def __init__(self, ttl: int = 300):

679

self.ttl = ttl

680

self.cache = {}

681

682

def cached_field(self, ttl: int = None):

683

def decorator(func):

684

@functools.wraps(func)

685

async def wrapper(*args, **kwargs):

686

cache_key = f"{func.__name__}:{hash(str(args + tuple(kwargs.items())))}"

687

688

if cache_key in self.cache:

689

value, timestamp = self.cache[cache_key]

690

if time.time() - timestamp < (ttl or self.ttl):

691

return value

692

693

result = await func(*args, **kwargs)

694

self.cache[cache_key] = (result, time.time())

695

return result

696

697

return wrapper

698

return decorator

699

700

# Usage

701

field_cache = FieldCache(ttl=600)

702

703

@strawberry.type

704

class User:

705

id: strawberry.ID

706

name: str

707

708

@field_cache.cached_field(ttl=300)

709

@strawberry.field

710

async def expensive_computation(self) -> str:

711

# Expensive operation that benefits from caching

712

await asyncio.sleep(1) # Simulate expensive operation

713

return f"computed_value_for_{self.id}"

714

```