or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconvenience-functions.mddiagram-creation.mdexceptions.mdindex.mdmodel-info.mdplugin-system.md

plugin-system.mddocs/

0

# Plugin System

1

2

Erdantic's plugin system provides extensible support for different data modeling libraries. The system allows registration of plugins that can identify and extract field information from various model types like Pydantic, attrs, dataclasses, and msgspec.

3

4

## Core Plugin Functions

5

6

```python { .api }

7

def list_plugins() -> list[str]:

8

"""List the keys of all registered plugins."""

9

10

def register_plugin(key: str, predicate_fn: ModelPredicate[_ModelType],

11

get_fields_fn: ModelFieldExtractor[_ModelType]):

12

"""Register a plugin for a specific model class type.

13

14

Args:

15

key (str): An identifier for this plugin.

16

predicate_fn (ModelPredicate): A predicate function to determine if an object is a class

17

of the model that is supported by this plugin.

18

get_fields_fn (ModelFieldExtractor): A function to extract fields from a model class that

19

is supported by this plugin.

20

"""

21

22

def get_predicate_fn(key: str) -> ModelPredicate:

23

"""Get the predicate function for a plugin by its key."""

24

25

def get_field_extractor_fn(key: str) -> ModelFieldExtractor:

26

"""Get the field extractor function for a plugin by its key."""

27

28

def identify_field_extractor_fn(tp: type) -> Optional[ModelFieldExtractor]:

29

"""Identify the field extractor function for a model type.

30

31

Args:

32

tp (type): A type annotation.

33

34

Returns:

35

ModelFieldExtractor | None: The field extractor function for a known model type, or None if

36

the model type is not recognized by any registered plugins.

37

"""

38

39

def load_plugins():

40

"""Load all core plugins.

41

42

This function is called automatically when erdantic is imported, but can be called

43

manually if needed. It attempts to load all core plugins (pydantic, attrs, dataclasses,

44

msgspec) and silently skips any with missing dependencies.

45

"""

46

```

47

48

## Plugin Protocol Types

49

50

```python { .api }

51

class ModelPredicate(Protocol[_ModelType_co]):

52

"""Protocol class for a predicate function for a plugin."""

53

54

def __call__(self, obj: Any) -> TypeGuard[_ModelType_co]: ...

55

56

class ModelFieldExtractor(Protocol[_ModelType_contra]):

57

"""Protocol class for a field extractor function for a plugin."""

58

59

def __call__(self, model: _ModelType_contra) -> Sequence[FieldInfo]: ...

60

```

61

62

## Required Imports

63

64

```python

65

from erdantic.plugins import (

66

list_plugins, register_plugin, get_predicate_fn,

67

get_field_extractor_fn, identify_field_extractor_fn,

68

load_plugins, ModelPredicate, ModelFieldExtractor

69

)

70

from erdantic.core import FieldInfo

71

from typing import Any, Optional, Sequence, TypeGuard, TypeVar, Protocol

72

```

73

74

## Built-in Plugins

75

76

Erdantic comes with built-in support for several popular data modeling libraries:

77

78

### Available Plugins

79

80

```python

81

# Check what plugins are currently available

82

from erdantic import list_plugins

83

print(list_plugins())

84

# Output: ['pydantic', 'pydantic_v1', 'dataclasses', 'attrs', 'msgspec']

85

```

86

87

### Pydantic Support

88

89

```python { .api }

90

def is_pydantic_model(obj: Any) -> TypeGuard[Type[pydantic.BaseModel]]:

91

"""Predicate function to determine if an object is a Pydantic model (not an instance)."""

92

93

def get_fields_from_pydantic_model(model: Type[pydantic.BaseModel]) -> list[FieldInfo]:

94

"""Given a Pydantic model, return a list of FieldInfo instances for each field in the model."""

95

96

def is_pydantic_v1_model(obj: Any) -> TypeGuard[Type[pydantic.v1.BaseModel]]:

97

"""Predicate function to determine if an object is a Pydantic V1 model."""

98

99

def get_fields_from_pydantic_v1_model(model: Type[pydantic.v1.BaseModel]) -> list[FieldInfo]:

100

"""Given a Pydantic V1 model, return a list of FieldInfo instances for each field in the model."""

101

```

102

103

## Usage Examples

104

105

### Listing Available Plugins

106

107

```python

108

from erdantic import list_plugins

109

110

# See all registered plugins

111

plugins = list_plugins()

112

print(f"Available plugins: {plugins}")

113

114

# Use in convenience functions to limit model search

115

from erdantic import draw

116

import my_models

117

118

# Only analyze Pydantic models in the module

119

draw(my_models, out="pydantic_only.png", limit_search_models_to=["pydantic"])

120

```

121

122

### Creating Custom Plugins

123

124

```python

125

from erdantic.plugins import register_plugin

126

from erdantic.core import FieldInfo, FullyQualifiedName

127

from typing import Any, TypeGuard

128

import inspect

129

130

# Example: Custom plugin for a hypothetical ORM

131

class MyORMModel:

132

"""Base class for custom ORM models."""

133

pass

134

135

def is_my_orm_model(obj: Any) -> TypeGuard[type]:

136

"""Check if object is a MyORM model class."""

137

return isinstance(obj, type) and issubclass(obj, MyORMModel)

138

139

def get_fields_from_my_orm_model(model: type) -> list[FieldInfo]:

140

"""Extract field information from MyORM model."""

141

fields = []

142

model_full_name = FullyQualifiedName.from_object(model)

143

144

# Example: Inspect class attributes that are field descriptors

145

for name, attr in inspect.getmembers(model):

146

if hasattr(attr, '__field_type__'): # Hypothetical field marker

147

field_info = FieldInfo.from_raw_type(

148

model_full_name=model_full_name,

149

name=name,

150

raw_type=attr.__field_type__

151

)

152

fields.append(field_info)

153

154

return fields

155

156

# Register the custom plugin

157

register_plugin(

158

key="my_orm",

159

predicate_fn=is_my_orm_model,

160

get_fields_fn=get_fields_from_my_orm_model

161

)

162

```

163

164

### Using Custom Plugins

165

166

```python

167

from erdantic import draw, list_plugins

168

169

# Verify custom plugin is registered

170

print(list_plugins()) # Should include "my_orm"

171

172

# Use the custom plugin

173

class User(MyORMModel):

174

# Custom field definitions

175

pass

176

177

class Post(MyORMModel):

178

# Custom field definitions

179

pass

180

181

# Generate diagram using custom models

182

draw(User, Post, out="custom_orm_models.png")

183

184

# Limit to only custom ORM models

185

draw(my_module, out="my_orm_only.png", limit_search_models_to=["my_orm"])

186

```

187

188

### Introspecting Plugin System

189

190

```python

191

from erdantic.plugins import get_predicate_fn, get_field_extractor_fn, identify_field_extractor_fn

192

from pydantic import BaseModel

193

194

# Get specific plugin functions

195

pydantic_predicate = get_predicate_fn("pydantic")

196

pydantic_extractor = get_field_extractor_fn("pydantic")

197

198

# Test predicate function

199

class MyModel(BaseModel):

200

name: str

201

202

print(pydantic_predicate(MyModel)) # True

203

print(pydantic_predicate(dict)) # False

204

205

# Get extractor for a specific model type

206

extractor = identify_field_extractor_fn(MyModel)

207

if extractor:

208

fields = extractor(MyModel)

209

for field in fields:

210

print(f"Field: {field.name} ({field.type_name})")

211

```

212

213

### Plugin-Based Model Discovery

214

215

```python

216

import inspect

217

from erdantic.plugins import identify_field_extractor_fn

218

219

def find_supported_models(module):

220

"""Find all classes in a module that are supported by registered plugins."""

221

supported_models = []

222

223

for name, obj in inspect.getmembers(module, inspect.isclass):

224

if obj.__module__ == module.__name__: # Only classes defined in this module

225

if identify_field_extractor_fn(obj) is not None:

226

supported_models.append(obj)

227

228

return supported_models

229

230

# Usage

231

import my_models

232

supported = find_supported_models(my_models)

233

print(f"Found {len(supported)} supported model classes")

234

```

235

236

### Error Handling

237

238

```python

239

from erdantic.plugins import get_predicate_fn, get_field_extractor_fn

240

from erdantic.exceptions import PluginNotFoundError

241

242

try:

243

# Try to get a non-existent plugin

244

predicate = get_predicate_fn("nonexistent")

245

except PluginNotFoundError as e:

246

print(f"Plugin not found: {e.key}")

247

248

try:

249

extractor = get_field_extractor_fn("nonexistent")

250

except PluginNotFoundError as e:

251

print(f"Plugin not found: {e.key}")

252

```

253

254

## Built-in Plugin Details

255

256

### Core Plugins Available

257

258

1. **`"pydantic"`** - Pydantic V2 models (`pydantic.BaseModel`)

259

2. **`"pydantic_v1"`** - Pydantic V1 legacy models (`pydantic.v1.BaseModel`)

260

3. **`"dataclasses"`** - Python standard library dataclasses

261

4. **`"attrs"`** - attrs library models

262

5. **`"msgspec"`** - msgspec library models

263

264

### Plugin Loading

265

266

```python

267

# Plugins are automatically loaded when erdantic is imported

268

import erdantic # This triggers plugin loading

269

270

# Manual plugin loading (normally not needed)

271

from erdantic.plugins import load_plugins

272

load_plugins()

273

```

274

275

### Plugin Dependencies

276

277

Some plugins require optional dependencies:

278

279

```python

280

# These will only be available if dependencies are installed:

281

# - "attrs" plugin requires: pip install attrs

282

# - "msgspec" plugin requires: pip install msgspec

283

284

# Check which plugins are actually active

285

from erdantic import list_plugins

286

active_plugins = list_plugins()

287

print(f"Active plugins: {active_plugins}")

288

```