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

breaking-changes.mddocs/

0

# Breaking Changes

1

2

Comprehensive API breakage detection system for identifying changes that could break backwards compatibility between different versions of Python packages. This system enables automated API compatibility checking in CI/CD pipelines and release workflows.

3

4

## Capabilities

5

6

### Breakage Detection

7

8

Main function for finding breaking changes between two API versions.

9

10

```python { .api }

11

def find_breaking_changes(

12

old_object: Object,

13

new_object: Object,

14

**kwargs: Any,

15

) -> Iterator[Breakage]:

16

"""

17

Find breaking changes between two versions of the same API.

18

19

Compares two Griffe objects (typically modules or packages) and identifies

20

changes that could break backwards compatibility for consumers of the API.

21

22

Args:

23

old_object: The previous version of the API

24

new_object: The current version of the API

25

**kwargs: Additional comparison options

26

27

Yields:

28

Breakage: Individual breaking changes found

29

30

Examples:

31

Compare Git versions:

32

>>> old_api = griffe.load_git("mypackage", ref="v1.0.0")

33

>>> new_api = griffe.load("mypackage")

34

>>> breakages = list(griffe.find_breaking_changes(old_api, new_api))

35

36

Compare PyPI versions:

37

>>> old_pypi = griffe.load_pypi("requests", "2.28.0")

38

>>> new_pypi = griffe.load_pypi("requests", "2.29.0")

39

>>> breakages = list(griffe.find_breaking_changes(old_pypi, new_pypi))

40

"""

41

```

42

43

### Base Breakage Class

44

45

Abstract base class for all API breakage types.

46

47

```python { .api }

48

class Breakage:

49

"""

50

Base class for API breakages.

51

52

Represents changes that could break backwards compatibility.

53

All specific breakage types inherit from this class.

54

"""

55

56

def __init__(

57

self,

58

object_path: str,

59

old_value: Any = None,

60

new_value: Any = None,

61

**kwargs: Any,

62

) -> None:

63

"""

64

Initialize the breakage.

65

66

Args:

67

object_path: Dotted path to the affected object

68

old_value: Previous value (if applicable)

69

new_value: New value (if applicable)

70

**kwargs: Additional breakage-specific data

71

"""

72

73

@property

74

def kind(self) -> BreakageKind:

75

"""The type/kind of this breakage."""

76

77

@property

78

def object_path(self) -> str:

79

"""Dotted path to the affected object."""

80

81

@property

82

def old_value(self) -> Any:

83

"""Previous value before the change."""

84

85

@property

86

def new_value(self) -> Any:

87

"""New value after the change."""

88

89

def explain(self) -> str:

90

"""

91

Get a human-readable explanation of the breakage.

92

93

Returns:

94

str: Description of what changed and why it's breaking

95

"""

96

```

97

98

## Object-Level Breakages

99

100

Breaking changes related to entire objects (removal, kind changes).

101

102

```python { .api }

103

class ObjectRemovedBreakage(Breakage):

104

"""

105

A public object was removed from the API.

106

107

This breakage occurs when a previously public class, function,

108

method, or attribute is no longer available.

109

"""

110

111

class ObjectChangedKindBreakage(Breakage):

112

"""

113

An object's kind changed (e.g., function became a class).

114

115

This breakage occurs when an object changes its fundamental type,

116

such as a function becoming a class or vice versa.

117

"""

118

```

119

120

## Parameter-Related Breakages

121

122

Breaking changes in function/method signatures.

123

124

```python { .api }

125

class ParameterAddedRequiredBreakage(Breakage):

126

"""

127

A required parameter was added to a function signature.

128

129

This breakage occurs when a new parameter without a default value

130

is added to a function, making existing calls invalid.

131

"""

132

133

class ParameterRemovedBreakage(Breakage):

134

"""

135

A parameter was removed from a function signature.

136

137

This breakage occurs when a parameter is removed entirely,

138

breaking code that passed that parameter.

139

"""

140

141

class ParameterChangedDefaultBreakage(Breakage):

142

"""

143

A parameter's default value changed.

144

145

This breakage occurs when the default value of a parameter changes,

146

potentially altering behavior for code that relies on the default.

147

"""

148

149

class ParameterChangedKindBreakage(Breakage):

150

"""

151

A parameter's kind changed (e.g., positional to keyword-only).

152

153

This breakage occurs when parameter calling conventions change,

154

such as making a positional parameter keyword-only.

155

"""

156

157

class ParameterChangedRequiredBreakage(Breakage):

158

"""

159

A parameter became required (lost its default value).

160

161

This breakage occurs when a parameter that previously had a default

162

value no longer has one, making it required.

163

"""

164

165

class ParameterMovedBreakage(Breakage):

166

"""

167

A parameter changed position in the function signature.

168

169

This breakage occurs when parameters are reordered, potentially

170

breaking positional argument usage.

171

"""

172

```

173

174

## Type-Related Breakages

175

176

Breaking changes in type annotations and return types.

177

178

```python { .api }

179

class ReturnChangedTypeBreakage(Breakage):

180

"""

181

A function's return type annotation changed.

182

183

This breakage occurs when the return type annotation of a function

184

changes in a way that could break type checking or expectations.

185

"""

186

187

class AttributeChangedTypeBreakage(Breakage):

188

"""

189

An attribute's type annotation changed.

190

191

This breakage occurs when an attribute's type annotation changes

192

in an incompatible way.

193

"""

194

195

class AttributeChangedValueBreakage(Breakage):

196

"""

197

An attribute's value changed.

198

199

This breakage occurs when the value of a constant or class attribute

200

changes, potentially breaking code that depends on the specific value.

201

"""

202

```

203

204

## Class-Related Breakages

205

206

Breaking changes in class hierarchies and inheritance.

207

208

```python { .api }

209

class ClassRemovedBaseBreakage(Breakage):

210

"""

211

A base class was removed from a class's inheritance.

212

213

This breakage occurs when a class no longer inherits from a base class,

214

potentially breaking isinstance() checks and inherited functionality.

215

"""

216

```

217

218

## Breakage Classification

219

220

```python { .api }

221

from enum import Enum

222

223

class BreakageKind(Enum):

224

"""

225

Enumeration of possible API breakage types.

226

227

Used to classify different kinds of breaking changes for

228

filtering and reporting purposes.

229

"""

230

231

# Object-level changes

232

OBJECT_REMOVED = "object_removed"

233

OBJECT_CHANGED_KIND = "object_changed_kind"

234

235

# Parameter changes

236

PARAMETER_ADDED_REQUIRED = "parameter_added_required"

237

PARAMETER_REMOVED = "parameter_removed"

238

PARAMETER_CHANGED_DEFAULT = "parameter_changed_default"

239

PARAMETER_CHANGED_KIND = "parameter_changed_kind"

240

PARAMETER_CHANGED_REQUIRED = "parameter_changed_required"

241

PARAMETER_MOVED = "parameter_moved"

242

243

# Type changes

244

RETURN_CHANGED_TYPE = "return_changed_type"

245

ATTRIBUTE_CHANGED_TYPE = "attribute_changed_type"

246

ATTRIBUTE_CHANGED_VALUE = "attribute_changed_value"

247

248

# Class hierarchy changes

249

CLASS_REMOVED_BASE = "class_removed_base"

250

```

251

252

## Usage Examples

253

254

### Basic Breaking Change Detection

255

256

```python

257

import griffe

258

259

# Compare two versions

260

old_version = griffe.load_git("mypackage", ref="v1.0.0")

261

new_version = griffe.load("mypackage")

262

263

# Find all breaking changes

264

breakages = list(griffe.find_breaking_changes(old_version, new_version))

265

266

print(f"Found {len(breakages)} breaking changes:")

267

for breakage in breakages:

268

print(f" {breakage.kind.value}: {breakage.object_path}")

269

print(f" {breakage.explain()}")

270

```

271

272

### Filtering Breaking Changes

273

274

```python

275

import griffe

276

from griffe import BreakageKind

277

278

# Get breaking changes

279

breakages = list(griffe.find_breaking_changes(old_api, new_api))

280

281

# Filter by type

282

removed_objects = [

283

b for b in breakages

284

if b.kind == BreakageKind.OBJECT_REMOVED

285

]

286

287

parameter_changes = [

288

b for b in breakages

289

if b.kind.value.startswith("parameter_")

290

]

291

292

type_changes = [

293

b for b in breakages

294

if "type" in b.kind.value

295

]

296

297

print(f"Removed objects: {len(removed_objects)}")

298

print(f"Parameter changes: {len(parameter_changes)}")

299

print(f"Type changes: {len(type_changes)}")

300

```

301

302

### CI/CD Integration

303

304

```python

305

import sys

306

import griffe

307

308

def check_api_compatibility(old_ref: str, package_name: str) -> int:

309

"""Check API compatibility and return exit code for CI."""

310

try:

311

# Load versions

312

old_api = griffe.load_git(package_name, ref=old_ref)

313

new_api = griffe.load(package_name)

314

315

# Find breaking changes

316

breakages = list(griffe.find_breaking_changes(old_api, new_api))

317

318

if breakages:

319

print(f"❌ Found {len(breakages)} breaking changes:")

320

for breakage in breakages:

321

print(f" • {breakage.explain()}")

322

return 1

323

else:

324

print("✅ No breaking changes detected")

325

return 0

326

327

except Exception as e:

328

print(f"❌ Error checking compatibility: {e}")

329

return 2

330

331

# Use in CI script

332

exit_code = check_api_compatibility("v1.0.0", "mypackage")

333

sys.exit(exit_code)

334

```

335

336

### Detailed Breakage Analysis

337

338

```python

339

import griffe

340

341

# Compare versions with detailed analysis

342

old_api = griffe.load_pypi("requests", "2.28.0")

343

new_api = griffe.load_pypi("requests", "2.29.0")

344

345

breakages = list(griffe.find_breaking_changes(old_api, new_api))

346

347

# Group by breakage type

348

by_type = {}

349

for breakage in breakages:

350

kind = breakage.kind.value

351

if kind not in by_type:

352

by_type[kind] = []

353

by_type[kind].append(breakage)

354

355

# Report by category

356

for breakage_type, items in by_type.items():

357

print(f"\n{breakage_type.replace('_', ' ').title()} ({len(items)}):")

358

for item in items:

359

print(f" • {item.object_path}")

360

if hasattr(item, 'old_value') and item.old_value is not None:

361

print(f" Old: {item.old_value}")

362

if hasattr(item, 'new_value') and item.new_value is not None:

363

print(f" New: {item.new_value}")

364

```

365

366

### Custom Breakage Analysis

367

368

```python

369

import griffe

370

from griffe import Breakage, BreakageKind

371

372

class CustomBreakageAnalyzer:

373

"""Custom analyzer for breaking changes."""

374

375

def __init__(self, ignore_patterns: list[str] = None):

376

self.ignore_patterns = ignore_patterns or []

377

378

def analyze(self, old_api, new_api) -> dict:

379

"""Analyze breaking changes with custom logic."""

380

breakages = list(griffe.find_breaking_changes(old_api, new_api))

381

382

# Filter ignored patterns

383

filtered_breakages = []

384

for breakage in breakages:

385

if not any(pattern in breakage.object_path for pattern in self.ignore_patterns):

386

filtered_breakages.append(breakage)

387

388

# Categorize by severity

389

critical = []

390

moderate = []

391

minor = []

392

393

for breakage in filtered_breakages:

394

if breakage.kind in [BreakageKind.OBJECT_REMOVED, BreakageKind.PARAMETER_ADDED_REQUIRED]:

395

critical.append(breakage)

396

elif breakage.kind in [BreakageKind.PARAMETER_CHANGED_KIND, BreakageKind.RETURN_CHANGED_TYPE]:

397

moderate.append(breakage)

398

else:

399

minor.append(breakage)

400

401

return {

402

"critical": critical,

403

"moderate": moderate,

404

"minor": minor,

405

"total": len(filtered_breakages)

406

}

407

408

# Use custom analyzer

409

analyzer = CustomBreakageAnalyzer(ignore_patterns=["_internal", "test_"])

410

results = analyzer.analyze(old_api, new_api)

411

412

print(f"Critical: {len(results['critical'])}")

413

print(f"Moderate: {len(results['moderate'])}")

414

print(f"Minor: {len(results['minor'])}")

415

```

416

417

## Types

418

419

```python { .api }

420

from typing import Any, Iterator

421

from enum import Enum

422

423

# Core types from models

424

from griffe import Object

425

426

# Breakage enumeration

427

class BreakageKind(Enum):

428

"""Enumeration of breakage types."""

429

430

# Base breakage type

431

class Breakage:

432

"""Base breakage class."""

433

```