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

model-info.mddocs/

0

# Model Information Classes

1

2

Erdantic uses several internal data structures to represent analyzed models, their fields, and relationships. These classes provide the foundation for diagram generation and can be used for advanced programmatic manipulation of diagram data.

3

4

## Core Information Classes

5

6

```python { .api }

7

class ModelInfo(pydantic.BaseModel):

8

"""Holds information about an analyzed data model class.

9

10

Attributes:

11

full_name (FullyQualifiedName): Fully qualified name of the data model class.

12

name (str): Name of the data model class.

13

fields (Dict[str, FieldInfo]): A mapping to FieldInfo instances for each field of the data

14

model class.

15

description (str): Docstring or other description of the data model class.

16

"""

17

18

@classmethod

19

def from_raw_model(cls, raw_model: type) -> Self:

20

"""Constructor method to create a new instance from a raw data model class.

21

22

Args:

23

raw_model (type): Data model class.

24

25

Returns:

26

Self: New instance of ModelInfo.

27

"""

28

29

@property

30

def key(self) -> str:

31

"""Returns the key used to identify this instance of ModelInfo in the

32

EntityRelationshipDiagram.models mapping. This value is the string representation of the

33

`full_name` field.

34

"""

35

36

@property

37

def raw_model(self) -> type:

38

"""Returns the raw data model class. This is a cached property. If the raw model is not

39

already known, it will attempt to import the data model class.

40

"""

41

42

def to_dot_label(self) -> str:

43

"""Returns the DOT language "HTML-like" syntax specification of a table for this data

44

model. It is used as the `label` attribute of data model's node in the graph's DOT

45

representation.

46

47

Returns:

48

str: DOT language for table

49

"""

50

51

class FieldInfo(pydantic.BaseModel):

52

"""Holds information about a field of an analyzed data model class.

53

54

Attributes:

55

model_full_name (FullyQualifiedName): Fully qualified name of the data model class that

56

the field belongs to.

57

name (str): Name of the field.

58

type_name (str): String representation of the field's type.

59

"""

60

61

@classmethod

62

def from_raw_type(cls, model_full_name: FullyQualifiedName, name: str, raw_type: type) -> Self:

63

"""Constructor method to create a new instance from a raw type annotation.

64

65

Args:

66

model_full_name (FullyQualifiedName): Fully qualified name of the data model class that

67

the field belongs to.

68

name (str): Name of field.

69

raw_type (type): Type annotation.

70

71

Returns:

72

Self: New FieldInfo instance.

73

"""

74

75

@property

76

def key(self) -> str:

77

"""Returns the key used to identify this instance of FieldInfo in the ModelInfo.fields

78

mapping. This value is the value in the 'name' field.

79

"""

80

81

@property

82

def raw_type(self) -> type:

83

"""Returns the raw type annotation of the field. This is a cached property. If the raw

84

type is not already known, it will attempt to import the data model class and reextract

85

the field's type annotation.

86

87

Raises:

88

FieldNotFoundError: If field name doesn't match any fields returned by field extractor.

89

UnknownModelTypeError: If model type is not recognized by any plugin.

90

91

Returns:

92

type: Type annotation.

93

"""

94

95

def to_dot_row(self) -> str:

96

"""Returns the DOT language "HTML-like" syntax specification of a row detailing this field

97

that is part of a table describing the field's parent data model. It is used as part the

98

`label` attribute of data model's node in the graph's DOT representation.

99

100

Returns:

101

str: DOT language for table row

102

"""

103

104

class Edge(pydantic.BaseModel):

105

"""Hold information about a relationship between two data model classes. These represent

106

directed edges in the entity relationship diagram.

107

108

Attributes:

109

source_model_full_name (FullyQualifiedName): Fully qualified name of the source model,

110

i.e., the model that contains a field that references the target model.

111

source_field_name (str): Name of the field on the source model that references the target

112

model.

113

target_model_full_name (FullyQualifiedName): Fully qualified name of the target model,

114

i.e., the model that is referenced by the source model's field.

115

target_cardinality (Cardinality): Cardinality of the target model in the relationship,

116

e.g., if the relationship is one (source) to many (target), this value will be

117

`Cardinality.MANY`.

118

target_modality (Modality): Modality of the target model in the relationship, e.g., if the

119

relationship is one (source) to zero (target), meaning that the target is optional,

120

this value will be `Modality.ZERO`.

121

source_cardinality (Cardinality): Cardinality of the source model in the

122

relationship. Defaults to Cardinality.UNSPECIFIED. This will never be set for Edges

123

created by erdantic, but you can set it manually to notate an externally known cardinality.

124

source_modality (Modality): Modality of the source model in the relationship.

125

Defaults to Modality.UNSPECIFIED. This will never be set for Edges created by erdantic,

126

but you can set it manually to notate an externally known modality.

127

"""

128

129

@property

130

def key(self) -> str:

131

"""Returns the key used to identify this instance of Edge in the

132

EntityRelationshipDiagram.edges mapping. This value is a hyphenated string of the fields

133

`source_model_full_name`, `source_field_name`, and `target_model_full_name`.

134

"""

135

136

@classmethod

137

def from_field_info(cls, target_model: type, source_field_info: FieldInfo) -> Self:

138

"""Constructor method to create a new instance from a target model instance and a source

139

model's FieldInfo.

140

141

Args:

142

target_model (type): Target model class.

143

source_field_info (FieldInfo): FieldInfo instance for the field on the source model

144

that references the target model.

145

146

Returns:

147

Self: New instance of Edge.

148

"""

149

150

def target_dot_arrow_shape(self) -> str:

151

"""Arrow shape specification in Graphviz DOT language for this edge's head (the end at the

152

target model). See [Graphviz docs](https://graphviz.org/doc/info/arrows.html) as a

153

reference. Shape returned is based on

154

[crow's foot notation](https://www.gleek.io/blog/crows-foot-notation) for the

155

relationship's cardinality and modality.

156

157

Returns:

158

str: DOT language specification for arrow shape of this edge's head

159

"""

160

161

def source_dot_arrow_shape(self) -> str:

162

"""Arrow shape specification in Graphviz DOT language for this edge's tail (the end at the

163

source model). See [Graphviz docs](https://graphviz.org/doc/info/arrows.html) as a

164

reference. Shape returned is based on

165

[crow's foot notation](https://www.gleek.io/blog/crows-foot-notation) for the

166

relationship's cardinality and modality.

167

168

Returns:

169

str: DOT language specification for arrow shape of this edge's tail

170

"""

171

172

@total_ordering

173

class FullyQualifiedName(pydantic.BaseModel):

174

"""Holds the fully qualified name components (module and qualified name) of a Python object.

175

This is used to uniquely identify an object, can be used to import it.

176

177

Attributes:

178

module (str): Name of the module that the object is defined in.

179

qual_name (str): Qualified name of the object.

180

"""

181

182

@classmethod

183

def from_object(cls, obj: Any) -> Self:

184

"""Constructor method to create a new instance from a Python object.

185

186

Args:

187

obj (Any): Python object.

188

189

Returns:

190

Self: Fully qualified name of the object.

191

"""

192

193

def import_object(self) -> Any:

194

"""Imports the object from the module and returns it.

195

196

Returns:

197

Any: Object referenced by this FullyQualifiedName instance.

198

"""

199

200

def __str__(self) -> str:

201

"""Returns string representation as 'module.qual_name'."""

202

203

def __hash__(self) -> int:

204

"""Returns hash based on module and qual_name."""

205

206

def __lt__(self, other: Self) -> bool:

207

"""Comparison operator for sorting."""

208

```

209

210

## Enumerations

211

212

```python { .api }

213

class Cardinality(Enum):

214

"""Enumeration of possible cardinality values for a relationship between two data model

215

classes. Cardinality measures the maximum number of associations.

216

"""

217

218

UNSPECIFIED = "unspecified"

219

ONE = "one"

220

MANY = "many"

221

222

def to_dot(self) -> str:

223

"""Returns the DOT language specification for the arrowhead styling associated with the

224

cardinality value.

225

"""

226

227

class Modality(Enum):

228

"""Enumeration of possible modality values for a relationship between two data model

229

classes. Modality measures the minimum number of associations.

230

"""

231

232

UNSPECIFIED = "unspecified"

233

ZERO = "zero"

234

ONE = "one"

235

236

def to_dot(self) -> str:

237

"""Returns the DOT language specification for the arrowhead styling associated with the

238

modality value.

239

"""

240

```

241

242

## Required Imports

243

244

```python

245

from erdantic.core import (

246

ModelInfo, FieldInfo, Edge, FullyQualifiedName,

247

Cardinality, Modality

248

)

249

from enum import Enum

250

from typing import Any

251

from functools import total_ordering

252

import pydantic

253

```

254

255

## Usage Examples

256

257

### Accessing Diagram Information

258

259

```python

260

from erdantic import create

261

from pydantic import BaseModel

262

263

class User(BaseModel):

264

name: str

265

email: str

266

267

class Post(BaseModel):

268

title: str

269

author: User

270

271

# Create diagram

272

diagram = create(User, Post)

273

274

# Access model information

275

for model_key, model_info in diagram.models.items():

276

print(f"Model: {model_info.name}")

277

print(f" Full name: {model_info.full_name}")

278

print(f" Description: {model_info.description}")

279

280

# Access field information

281

for field_name, field_info in model_info.fields.items():

282

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

283

284

# Access relationship information

285

for edge_key, edge in diagram.edges.items():

286

print(f"Relationship: {edge.source_model_full_name}.{edge.source_field_name} -> {edge.target_model_full_name}")

287

print(f" Target cardinality: {edge.target_cardinality.value}")

288

print(f" Target modality: {edge.target_modality.value}")

289

```

290

291

### Working with FullyQualifiedName

292

293

```python

294

from erdantic.core import FullyQualifiedName

295

296

# Create from object

297

fqn = FullyQualifiedName.from_object(User)

298

print(f"Module: {fqn.module}") # __main__ (or actual module)

299

print(f"Qualified name: {fqn.qual_name}") # User

300

print(f"Full name: {str(fqn)}") # __main__.User

301

302

# Import object back

303

imported_class = fqn.import_object()

304

print(imported_class == User) # True

305

```

306

307

### Manual ModelInfo Creation

308

309

```python

310

from erdantic.core import ModelInfo, FieldInfo, FullyQualifiedName

311

312

# Create model info manually (normally done automatically)

313

model_fqn = FullyQualifiedName.from_object(User)

314

model_info = ModelInfo.from_raw_model(User)

315

316

print(f"Model name: {model_info.name}")

317

print(f"Model key: {model_info.key}")

318

print(f"Fields: {list(model_info.fields.keys())}")

319

320

# Access the original model class

321

original_model = model_info.raw_model

322

print(original_model == User) # True

323

```

324

325

### Manual FieldInfo Creation

326

327

```python

328

from erdantic.core import FieldInfo, FullyQualifiedName

329

330

# Create field info manually

331

model_fqn = FullyQualifiedName.from_object(User)

332

field_info = FieldInfo.from_raw_type(

333

model_full_name=model_fqn,

334

name="name",

335

raw_type=str

336

)

337

338

print(f"Field name: {field_info.name}")

339

print(f"Field type: {field_info.type_name}")

340

print(f"Field key: {field_info.key}")

341

342

# Access raw type

343

raw_type = field_info.raw_type

344

print(raw_type == str) # True

345

```

346

347

### Understanding Relationships

348

349

```python

350

# Examine edge details

351

for edge in diagram.edges.values():

352

print(f"Source: {edge.source_model_full_name}")

353

print(f"Source field: {edge.source_field_name}")

354

print(f"Target: {edge.target_model_full_name}")

355

356

# Cardinality and modality

357

print(f"Target cardinality: {edge.target_cardinality}") # ONE or MANY

358

print(f"Target modality: {edge.target_modality}") # ZERO, ONE, or UNSPECIFIED

359

360

# DOT arrow shapes

361

print(f"Target arrow: {edge.target_dot_arrow_shape()}")

362

print(f"Source arrow: {edge.source_dot_arrow_shape()}")

363

```

364

365

### Custom Edge Creation

366

367

```python

368

from erdantic.core import Edge, Cardinality, Modality, FullyQualifiedName

369

370

# Create custom edge manually

371

user_fqn = FullyQualifiedName.from_object(User)

372

post_fqn = FullyQualifiedName.from_object(Post)

373

374

custom_edge = Edge(

375

source_model_full_name=post_fqn,

376

source_field_name="author",

377

target_model_full_name=user_fqn,

378

target_cardinality=Cardinality.ONE,

379

target_modality=Modality.ONE,

380

source_cardinality=Cardinality.MANY, # Optional: many posts per user

381

source_modality=Modality.ZERO # Optional: user might have no posts

382

)

383

384

print(f"Edge key: {custom_edge.key}")

385

```

386

387

### DOT Generation from Components

388

389

```python

390

# Generate DOT representations of individual components

391

model_info = diagram.models[str(FullyQualifiedName.from_object(User))]

392

dot_label = model_info.to_dot_label()

393

print("Model DOT label:", dot_label)

394

395

field_info = model_info.fields["name"]

396

dot_row = field_info.to_dot_row()

397

print("Field DOT row:", dot_row)

398

```

399

400

### Working with Cardinality and Modality

401

402

```python

403

from erdantic.core import Cardinality, Modality

404

405

# All cardinality values

406

print("Cardinalities:")

407

for card in Cardinality:

408

print(f" {card.value}: {card.to_dot()}")

409

410

# All modality values

411

print("Modalities:")

412

for mod in Modality:

413

print(f" {mod.value}: {mod.to_dot()}")

414

415

# Combined arrow shapes (cardinality + modality)

416

one_required = Cardinality.ONE.to_dot() + Modality.ONE.to_dot()

417

many_optional = Cardinality.MANY.to_dot() + Modality.ZERO.to_dot()

418

print(f"One required: {one_required}") # noneteetee

419

print(f"Many optional: {many_optional}") # crowodot

420

```