or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

agents.mdbreaking-changes.mdcli.mddocstrings.mdextensions.mdindex.mdloaders.mdmodels.mdserialization.md

extensions.mddocs/

0

# Extensions

1

2

Extensible plugin system for customizing and enhancing Griffe's analysis capabilities. Extensions allow developers to add custom processing during code analysis, handle special decorators, implement domain-specific analysis, and extend Griffe's understanding of Python constructs.

3

4

## Capabilities

5

6

### Extension Loading

7

8

Functions for loading and managing extensions from various sources.

9

10

```python { .api }

11

def load_extensions(

12

extensions: list[LoadableExtensionType] | None = None

13

) -> Extensions:

14

"""

15

Load configured extensions from various sources.

16

17

Supports loading built-in extensions, installed packages, and custom

18

extension classes. Extensions are executed during different phases

19

of the analysis process.

20

21

Args:

22

extensions: List of extension specifications including:

23

- String names for built-in extensions ("dataclasses")

24

- Import paths for installed extensions ("package.module:ExtensionClass")

25

- Extension class instances

26

- Extension class types

27

28

Returns:

29

Extensions: Container with loaded and configured extensions

30

31

Raises:

32

ExtensionNotLoadedError: If extension cannot be loaded

33

34

Examples:

35

Load built-in extensions:

36

>>> extensions = griffe.load_extensions(["dataclasses"])

37

38

Load custom extensions:

39

>>> extensions = griffe.load_extensions([

40

... "dataclasses", # built-in

41

... "mypackage.extensions:CustomAnalyzer", # installed

42

... MyExtension(), # instance

43

... ])

44

"""

45

46

# Type alias for extension specifications

47

LoadableExtensionType = (

48

str | # Extension name or import path

49

type[Extension] | # Extension class

50

Extension | # Extension instance

51

dict[str, Any] # Extension configuration

52

)

53

54

# Built-in extensions registry

55

builtin_extensions: dict[str, type[Extension]]

56

"""Dictionary of built-in extensions available in Griffe."""

57

```

58

59

### Base Extension Class

60

61

Foundation class for creating custom extensions with lifecycle hooks.

62

63

```python { .api }

64

class Extension:

65

"""

66

Base class for Griffe extensions.

67

68

Allows custom processing during code analysis by providing hooks

69

at different stages of the loading and analysis process.

70

"""

71

72

def __init__(self, **options: Any) -> None:

73

"""

74

Initialize the extension.

75

76

Args:

77

**options: Extension-specific configuration options

78

"""

79

80

def on_package_loaded(self, *, pkg: Module) -> None:

81

"""

82

Hook called when a package is loaded.

83

84

Called after a top-level package (module) has been fully loaded

85

and all its contents analyzed. Use this for package-level processing.

86

87

Args:

88

pkg: The loaded package module

89

"""

90

91

def on_module_loaded(self, *, mod: Module) -> None:

92

"""

93

Hook called when a module is loaded.

94

95

Called after a module has been loaded and its contents analyzed.

96

Includes both top-level packages and submodules.

97

98

Args:

99

mod: The loaded module

100

"""

101

102

def on_class_loaded(self, *, cls: Class) -> None:

103

"""

104

Hook called when a class is loaded.

105

106

Called after a class definition has been analyzed and all its

107

methods, attributes, and nested classes have been processed.

108

109

Args:

110

cls: The loaded class

111

"""

112

113

def on_function_loaded(self, *, func: Function) -> None:

114

"""

115

Hook called when a function is loaded.

116

117

Called after a function or method has been analyzed, including

118

parameter extraction and signature processing.

119

120

Args:

121

func: The loaded function or method

122

"""

123

124

def on_attribute_loaded(self, *, attr: Attribute) -> None:

125

"""

126

Hook called when an attribute is loaded.

127

128

Called after an attribute (variable, class attribute, etc.)

129

has been analyzed and its type and value determined.

130

131

Args:

132

attr: The loaded attribute

133

"""

134

135

def on_alias_loaded(self, *, alias: Alias) -> None:

136

"""

137

Hook called when an alias is loaded.

138

139

Called after an import alias has been processed and its

140

target path determined.

141

142

Args:

143

alias: The loaded alias

144

"""

145

```

146

147

### Extensions Container

148

149

Container class for managing multiple extensions and their execution.

150

151

```python { .api }

152

class Extensions:

153

"""

154

Container class for managing multiple extensions.

155

156

Manages a collection of extensions and provides methods to execute

157

their hooks at appropriate times during analysis.

158

"""

159

160

def __init__(self, extensions: list[Extension] | None = None) -> None:

161

"""

162

Initialize extensions container.

163

164

Args:

165

extensions: List of extension instances

166

"""

167

168

def add(self, extension: Extension) -> None:

169

"""

170

Add an extension to the container.

171

172

Args:

173

extension: Extension instance to add

174

"""

175

176

def call(self, method: str, **kwargs: Any) -> None:

177

"""

178

Call a method on all extensions.

179

180

Args:

181

method: Method name to call (e.g., "on_class_loaded")

182

**kwargs: Arguments to pass to the method

183

"""

184

185

def __iter__(self) -> Iterator[Extension]:

186

"""Iterate over extensions."""

187

188

def __len__(self) -> int:

189

"""Number of extensions."""

190

```

191

192

## Built-in Extensions

193

194

### Dataclasses Extension

195

196

Built-in extension for enhanced analysis of Python dataclasses.

197

198

```python { .api }

199

class DataclassesExtension(Extension):

200

"""

201

Built-in extension for handling Python dataclasses.

202

203

Provides enhanced analysis of dataclass decorators, field definitions,

204

and generated methods. Automatically detects dataclass usage and

205

extracts field information with types and defaults.

206

"""

207

208

def __init__(self, **options: Any) -> None:

209

"""

210

Initialize dataclasses extension.

211

212

Args:

213

**options: Configuration options for dataclass analysis

214

"""

215

216

def on_class_loaded(self, *, cls: Class) -> None:

217

"""

218

Process dataclass when class is loaded.

219

220

Analyzes @dataclass decorators and extracts field information,

221

adding synthetic attributes for dataclass fields.

222

"""

223

```

224

225

## Usage Examples

226

227

### Using Built-in Extensions

228

229

```python

230

import griffe

231

232

# Load with dataclasses extension

233

extensions = griffe.load_extensions(["dataclasses"])

234

loader = griffe.GriffeLoader(extensions=extensions)

235

236

# Load a package that uses dataclasses

237

package = loader.load("mypackage")

238

239

# The dataclasses extension will have processed any @dataclass decorators

240

for class_name, cls in package.classes.items():

241

if any("dataclass" in str(dec.value) for dec in cls.decorators):

242

print(f"Dataclass: {class_name}")

243

print(f" Fields: {list(cls.attributes.keys())}")

244

```

245

246

### Creating Custom Extensions

247

248

```python

249

import griffe

250

from griffe import Extension, Class, Function

251

252

class LoggingExtension(Extension):

253

"""Extension that logs all loaded objects."""

254

255

def __init__(self, log_level="INFO", **options):

256

super().__init__(**options)

257

self.log_level = log_level

258

self.stats = {"modules": 0, "classes": 0, "functions": 0}

259

260

def on_module_loaded(self, *, mod):

261

self.stats["modules"] += 1

262

print(f"[{self.log_level}] Loaded module: {mod.path}")

263

264

def on_class_loaded(self, *, cls):

265

self.stats["classes"] += 1

266

print(f"[{self.log_level}] Loaded class: {cls.path}")

267

268

# Log inheritance information

269

if cls.bases:

270

base_names = [str(base) for base in cls.bases]

271

print(f" Inherits from: {', '.join(base_names)}")

272

273

def on_function_loaded(self, *, func):

274

self.stats["functions"] += 1

275

print(f"[{self.log_level}] Loaded function: {func.path}")

276

277

# Log parameter information

278

param_count = len(func.parameters)

279

print(f" Parameters: {param_count}")

280

281

# Use the custom extension

282

logging_ext = LoggingExtension(log_level="DEBUG")

283

extensions = griffe.Extensions([logging_ext])

284

285

loader = griffe.GriffeLoader(extensions=extensions)

286

package = loader.load("requests")

287

288

print("Final stats:", logging_ext.stats)

289

```

290

291

### Domain-Specific Analysis Extension

292

293

```python

294

import griffe

295

from griffe import Extension, Function, Class

296

297

class FastAPIExtension(Extension):

298

"""Extension for analyzing FastAPI applications."""

299

300

def __init__(self, **options):

301

super().__init__(**options)

302

self.routes = []

303

self.dependencies = []

304

self.middleware = []

305

306

def on_function_loaded(self, *, func: Function):

307

"""Analyze FastAPI route decorators."""

308

for decorator in func.decorators:

309

decorator_str = str(decorator.value)

310

311

# Check for route decorators

312

if any(method in decorator_str for method in ["get", "post", "put", "delete", "patch"]):

313

route_info = {

314

"function": func.path,

315

"decorator": decorator_str,

316

"parameters": [p.name for p in func.parameters],

317

"returns": str(func.returns) if func.returns else None

318

}

319

self.routes.append(route_info)

320

print(f"Found route: {func.name} -> {decorator_str}")

321

322

# Check for dependency injection

323

elif "Depends" in decorator_str:

324

self.dependencies.append({

325

"function": func.path,

326

"dependency": decorator_str

327

})

328

329

def on_class_loaded(self, *, cls: Class):

330

"""Analyze FastAPI middleware classes."""

331

for decorator in cls.decorators:

332

if "middleware" in str(decorator.value).lower():

333

self.middleware.append({

334

"class": cls.path,

335

"decorator": str(decorator.value)

336

})

337

338

def get_api_summary(self):

339

"""Get summary of FastAPI application structure."""

340

return {

341

"routes": len(self.routes),

342

"dependencies": len(self.dependencies),

343

"middleware": len(self.middleware),

344

"route_details": self.routes

345

}

346

347

# Use FastAPI extension

348

fastapi_ext = FastAPIExtension()

349

extensions = griffe.Extensions([fastapi_ext])

350

351

loader = griffe.GriffeLoader(extensions=extensions)

352

app_module = loader.load("myapp")

353

354

# Get API analysis results

355

summary = fastapi_ext.get_api_summary()

356

print(f"FastAPI Analysis: {summary['routes']} routes, {summary['dependencies']} dependencies")

357

```

358

359

### Type Annotation Extension

360

361

```python

362

import griffe

363

from griffe import Extension, Function, Attribute

364

import ast

365

366

class TypeAnnotationExtension(Extension):

367

"""Extension for enhanced type annotation analysis."""

368

369

def __init__(self, **options):

370

super().__init__(**options)

371

self.type_stats = {

372

"annotated_functions": 0,

373

"unannotated_functions": 0,

374

"annotated_attributes": 0,

375

"complex_types": 0

376

}

377

378

def on_function_loaded(self, *, func: Function):

379

"""Analyze function type annotations."""

380

if func.returns or any(p.annotation for p in func.parameters):

381

self.type_stats["annotated_functions"] += 1

382

383

# Check for complex return types

384

if func.returns:

385

return_str = str(func.returns)

386

if any(keyword in return_str for keyword in ["Union", "Optional", "Generic", "TypeVar"]):

387

self.type_stats["complex_types"] += 1

388

print(f"Complex return type in {func.name}: {return_str}")

389

else:

390

self.type_stats["unannotated_functions"] += 1

391

392

def on_attribute_loaded(self, *, attr: Attribute):

393

"""Analyze attribute type annotations."""

394

if attr.annotation:

395

self.type_stats["annotated_attributes"] += 1

396

397

# Check for complex attribute types

398

attr_str = str(attr.annotation)

399

if any(keyword in attr_str for keyword in ["Union", "Optional", "List", "Dict"]):

400

print(f"Complex attribute type: {attr.path}: {attr_str}")

401

402

def get_type_coverage(self):

403

"""Calculate type annotation coverage."""

404

total_functions = self.type_stats["annotated_functions"] + self.type_stats["unannotated_functions"]

405

if total_functions > 0:

406

coverage = (self.type_stats["annotated_functions"] / total_functions) * 100

407

return {

408

"function_coverage": round(coverage, 2),

409

"annotated_functions": self.type_stats["annotated_functions"],

410

"total_functions": total_functions,

411

"complex_types": self.type_stats["complex_types"],

412

"annotated_attributes": self.type_stats["annotated_attributes"]

413

}

414

return {"function_coverage": 0, "total_functions": 0}

415

416

# Use type annotation extension

417

type_ext = TypeAnnotationExtension()

418

extensions = griffe.Extensions([type_ext])

419

420

loader = griffe.GriffeLoader(extensions=extensions)

421

package = loader.load("mypackage")

422

423

coverage = type_ext.get_type_coverage()

424

print(f"Type annotation coverage: {coverage['function_coverage']}%")

425

print(f"Complex types found: {coverage['complex_types']}")

426

```

427

428

### Extension with Configuration

429

430

```python

431

import griffe

432

from griffe import Extension

433

434

class ConfigurableExtension(Extension):

435

"""Extension with comprehensive configuration options."""

436

437

def __init__(

438

self,

439

track_private=False,

440

ignore_patterns=None,

441

output_file=None,

442

**options

443

):

444

super().__init__(**options)

445

self.track_private = track_private

446

self.ignore_patterns = ignore_patterns or []

447

self.output_file = output_file

448

self.data = []

449

450

def should_process(self, obj_path: str) -> bool:

451

"""Check if object should be processed based on configuration."""

452

# Skip private objects if not tracking them

453

if not self.track_private and any(part.startswith("_") for part in obj_path.split(".")):

454

return False

455

456

# Skip ignored patterns

457

if any(pattern in obj_path for pattern in self.ignore_patterns):

458

return False

459

460

return True

461

462

def on_function_loaded(self, *, func):

463

if self.should_process(func.path):

464

self.data.append({

465

"type": "function",

466

"path": func.path,

467

"parameters": len(func.parameters),

468

"has_docstring": func.docstring is not None

469

})

470

471

def on_class_loaded(self, *, cls):

472

if self.should_process(cls.path):

473

self.data.append({

474

"type": "class",

475

"path": cls.path,

476

"methods": len(cls.methods),

477

"attributes": len(cls.attributes),

478

"bases": len(cls.bases)

479

})

480

481

def finalize(self):

482

"""Called after all loading is complete."""

483

if self.output_file:

484

import json

485

with open(self.output_file, "w") as f:

486

json.dump(self.data, f, indent=2)

487

print(f"Extension data written to {self.output_file}")

488

489

# Configure and use extension

490

ext = ConfigurableExtension(

491

track_private=False,

492

ignore_patterns=["test_", "_internal"],

493

output_file="analysis_results.json"

494

)

495

496

extensions = griffe.Extensions([ext])

497

loader = griffe.GriffeLoader(extensions=extensions)

498

package = loader.load("mypackage")

499

500

# Finalize extension processing

501

ext.finalize()

502

```

503

504

## Advanced Extension Patterns

505

506

### Multi-Phase Extension

507

508

```python

509

import griffe

510

from griffe import Extension

511

512

class MultiPhaseExtension(Extension):

513

"""Extension that processes objects in multiple phases."""

514

515

def __init__(self, **options):

516

super().__init__(**options)

517

self.phase = "collection"

518

self.collected_objects = {"classes": [], "functions": []}

519

self.relationships = []

520

521

def on_class_loaded(self, *, cls):

522

if self.phase == "collection":

523

self.collected_objects["classes"].append(cls)

524

525

def on_function_loaded(self, *, func):

526

if self.phase == "collection":

527

self.collected_objects["functions"].append(func)

528

529

def on_package_loaded(self, *, pkg):

530

"""Switch to relationship analysis phase."""

531

if self.phase == "collection":

532

self.phase = "analysis"

533

self._analyze_relationships()

534

535

def _analyze_relationships(self):

536

"""Analyze relationships between collected objects."""

537

for cls in self.collected_objects["classes"]:

538

# Check inheritance relationships

539

for base in cls.bases:

540

base_name = str(base)

541

matching_classes = [

542

c for c in self.collected_objects["classes"]

543

if c.name == base_name.split(".")[-1]

544

]

545

if matching_classes:

546

self.relationships.append({

547

"type": "inheritance",

548

"child": cls.path,

549

"parent": matching_classes[0].path

550

})

551

552

print(f"Found {len(self.relationships)} relationships")

553

554

# Use multi-phase extension

555

multi_ext = MultiPhaseExtension()

556

extensions = griffe.Extensions([multi_ext])

557

558

loader = griffe.GriffeLoader(extensions=extensions)

559

package = loader.load("mypackage")

560

561

print("Relationships:", multi_ext.relationships)

562

```

563

564

## Types

565

566

```python { .api }

567

from typing import Any, Iterator, Union

568

569

# Extension type specifications

570

LoadableExtensionType = Union[

571

str, # Extension name or import path

572

type[Extension], # Extension class

573

Extension, # Extension instance

574

dict[str, Any] # Extension configuration

575

]

576

577

# Core extension classes

578

class Extension: ...

579

class Extensions: ...

580

581

# Built-in extensions registry

582

builtin_extensions: dict[str, type[Extension]]

583

584

# Built-in extension classes

585

class DataclassesExtension(Extension): ...

586

587

# Core object types from models

588

from griffe import Module, Class, Function, Attribute, Alias

589

```