or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmodel-annotations.mdmonkey-patching.mdprotocols.mdqueryset-types.mdstring-types.md

protocols.mddocs/

0

# Protocols and Base Classes

1

2

Protocol definitions for flexible attribute access and typed base classes for Django components. These provide structural typing support and typed interfaces for Django's dynamic components like Model.Meta classes and database routers.

3

4

## Capabilities

5

6

### Flexible Attribute Access Protocol

7

8

A protocol defining arbitrary attribute access patterns, used internally by the mypy Django plugin for dynamic attribute handling.

9

10

```python { .api }

11

class AnyAttrAllowed(Protocol):

12

"""

13

Protocol for classes allowing arbitrary attribute access.

14

15

Used internally by mypy_django_plugin to handle dynamic attributes

16

on Django models and other classes that support flexible attribute access.

17

"""

18

19

def __getattr__(self, item: str) -> Any:

20

"""Allow getting any attribute by name."""

21

...

22

23

def __setattr__(self, item: str, value: Any) -> None:

24

"""Allow setting any attribute by name."""

25

...

26

```

27

28

### Typed Model Meta Base Class

29

30

A typed base class for Django Model Meta inner classes, providing comprehensive type safety for model metadata configuration.

31

32

```python { .api }

33

class TypedModelMeta:

34

"""

35

Typed base class for Django Model `class Meta:` inner class.

36

At runtime this is just an alias to `object`.

37

38

Most attributes are the same as `django.db.models.options.Options`.

39

Options has some additional attributes and some values are normalized by Django.

40

"""

41

42

abstract: ClassVar[bool] # default: False

43

app_label: ClassVar[str]

44

base_manager_name: ClassVar[str]

45

db_table: ClassVar[str]

46

db_table_comment: ClassVar[str]

47

db_tablespace: ClassVar[str]

48

default_manager_name: ClassVar[str]

49

default_related_name: ClassVar[str]

50

get_latest_by: ClassVar[Union[str, Sequence[str]]]

51

managed: ClassVar[bool] # default: True

52

order_with_respect_to: ClassVar[str]

53

ordering: ClassVar[Sequence[Union[str, OrderBy]]]

54

permissions: ClassVar[List[Tuple[str, str]]]

55

default_permissions: ClassVar[Sequence[str]] # default: ("add", "change", "delete", "view")

56

proxy: ClassVar[bool] # default: False

57

required_db_features: ClassVar[List[str]]

58

required_db_vendor: ClassVar[Literal["sqlite", "postgresql", "mysql", "oracle"]]

59

select_on_save: ClassVar[bool] # default: False

60

indexes: ClassVar[List[Index]]

61

unique_together: ClassVar[Union[Sequence[Sequence[str]], Sequence[str]]]

62

index_together: ClassVar[Union[Sequence[Sequence[str]], Sequence[str]]] # Deprecated in Django 4.2

63

constraints: ClassVar[List[BaseConstraint]]

64

verbose_name: ClassVar[StrOrPromise]

65

verbose_name_plural: ClassVar[StrOrPromise]

66

```

67

68

### Typed Database Router Base Class

69

70

A typed base class for Django's DATABASE_ROUTERS setting, providing comprehensive type safety for database routing logic.

71

72

```python { .api }

73

class TypedDatabaseRouter:

74

"""

75

Typed base class for Django's DATABASE_ROUTERS setting.

76

At runtime this is just an alias to `object`.

77

78

All methods are optional.

79

80

Django documentation: https://docs.djangoproject.com/en/stable/topics/db/multi-db/#automatic-database-routing

81

"""

82

83

def db_for_read(self, model: Type[Model], **hints: Any) -> Optional[str]:

84

"""Suggest the database to read from for objects of type model."""

85

...

86

87

def db_for_write(self, model: Type[Model], **hints: Any) -> Optional[str]:

88

"""Suggest the database to write to for objects of type model."""

89

...

90

91

def allow_relation(self, obj1: Type[Model], obj2: Type[Model], **hints: Any) -> Optional[bool]:

92

"""Return True if a relation between obj1 and obj2 should be allowed."""

93

...

94

95

def allow_migrate(

96

self, db: str, app_label: str, model_name: Optional[str] = None, **hints: Any

97

) -> Optional[bool]:

98

"""Return True if the migration operation is allowed on the database with alias db."""

99

...

100

```

101

102

### Related Manager Protocols

103

104

Protocol definitions for Django's dynamically-created related managers, providing type safety for relationship management. These are re-exported from django-stubs for convenience.

105

106

```python { .api }

107

# During TYPE_CHECKING: imported from django-stubs

108

# At runtime: Protocol definitions to prevent isinstance() usage

109

110

class RelatedManager(Protocol[_T]):

111

"""

112

Protocol for Django related managers.

113

114

These are fake classes - Django defines these inside function bodies.

115

Defined as Protocol to prevent use with isinstance().

116

These actually inherit from BaseManager.

117

"""

118

pass

119

120

class ManyRelatedManager(Protocol[_T]):

121

"""

122

Protocol for Django many-to-many related managers.

123

124

These are fake classes - Django defines these inside function bodies.

125

Defined as Protocol to prevent use with isinstance().

126

These actually inherit from BaseManager.

127

"""

128

pass

129

```

130

131

### Usage Examples

132

133

Using TypedModelMeta for model configuration:

134

135

```python

136

from django_stubs_ext.db.models import TypedModelMeta

137

from django.db import models

138

from django.utils.translation import gettext_lazy as _

139

140

class Article(models.Model):

141

title = models.CharField(max_length=200)

142

content = models.TextField()

143

created_at = models.DateTimeField(auto_now_add=True)

144

145

class Meta(TypedModelMeta):

146

verbose_name = _('Article')

147

verbose_name_plural = _('Articles')

148

ordering = ['-created_at']

149

indexes = [

150

models.Index(fields=['created_at']),

151

models.Index(fields=['title'])

152

]

153

permissions = [

154

('can_publish', 'Can publish articles'),

155

('can_feature', 'Can feature articles')

156

]

157

```

158

159

Implementing a typed database router:

160

161

```python

162

from django_stubs_ext.db.router import TypedDatabaseRouter

163

from django.db.models import Model

164

from typing import Optional, Type, Any

165

166

class DatabaseRouter(TypedDatabaseRouter):

167

"""Route reads and writes based on model type."""

168

169

def db_for_read(self, model: Type[Model], **hints: Any) -> Optional[str]:

170

"""Route read operations."""

171

if model._meta.app_label == 'analytics':

172

return 'analytics_db'

173

return None

174

175

def db_for_write(self, model: Type[Model], **hints: Any) -> Optional[str]:

176

"""Route write operations."""

177

if model._meta.app_label == 'analytics':

178

return 'analytics_db'

179

return None

180

181

def allow_relation(self, obj1: Type[Model], obj2: Type[Model], **hints: Any) -> Optional[bool]:

182

"""Allow relations within the same app."""

183

db_set = {'default', 'analytics_db'}

184

if hints.get('instance1')._meta.app_label in db_set:

185

return hints.get('instance2')._meta.app_label in db_set

186

return None

187

188

def allow_migrate(

189

self, db: str, app_label: str, model_name: Optional[str] = None, **hints: Any

190

) -> Optional[bool]:

191

"""Ensure analytics models only appear in analytics_db."""

192

if app_label == 'analytics':

193

return db == 'analytics_db'

194

elif db == 'analytics_db':

195

return False

196

return None

197

```

198

199

Using related manager protocols:

200

201

```python

202

from django_stubs_ext.db.models.manager import RelatedManager, ManyRelatedManager

203

from django.db import models

204

from django.db.models import QuerySet

205

from typing import TYPE_CHECKING

206

207

class Author(models.Model):

208

name = models.CharField(max_length=100)

209

210

class Publisher(models.Model):

211

name = models.CharField(max_length=100)

212

213

class Book(models.Model):

214

title = models.CharField(max_length=200)

215

authors = models.ManyToManyField(Author, related_name='books')

216

publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, related_name='books')

217

218

if TYPE_CHECKING:

219

# Type annotations for related managers

220

Author.books: ManyRelatedManager[Book]

221

Publisher.books: RelatedManager[Book]

222

223

# Type-safe usage

224

def get_author_books(author: Author) -> QuerySet[Book]:

225

"""Get all books by an author."""

226

return author.books.all() # Type checker knows this returns QuerySet[Book]

227

```

228

229

### AnyAttrAllowed Protocol Usage

230

231

The AnyAttrAllowed protocol is primarily used internally by the mypy Django plugin:

232

233

```python

234

from django_stubs_ext import AnyAttrAllowed

235

from typing import Any

236

237

class DynamicModel(AnyAttrAllowed):

238

"""Example class with flexible attribute access."""

239

240

def __init__(self):

241

self._data = {}

242

243

def __getattr__(self, item: str) -> Any:

244

return self._data.get(item)

245

246

def __setattr__(self, item: str, value: Any) -> None:

247

if item.startswith('_'):

248

super().__setattr__(item, value)

249

else:

250

self._data[item] = value

251

252

# Type-safe dynamic attribute access

253

model = DynamicModel()

254

model.any_attribute = "value" # Type checker allows this

255

retrieved = model.any_attribute # Type checker knows this is Any

256

```

257

258

## Types

259

260

```python { .api }

261

from typing import TYPE_CHECKING, Any, ClassVar, List, Literal, Optional, Protocol, Sequence, Tuple, Type, TypeVar, Union

262

from django.db.models import BaseConstraint, Index, Model, OrderBy

263

from django_stubs_ext import StrOrPromise

264

265

_T = TypeVar("_T")

266

267

if TYPE_CHECKING:

268

class TypedModelMeta:

269

"""Typed base class for Django Model `class Meta:` inner class."""

270

# ... (all the attributes as shown above)

271

272

class TypedDatabaseRouter:

273

"""Typed base class for Django's DATABASE_ROUTERS setting."""

274

# ... (all the methods as shown above)

275

else:

276

TypedModelMeta = object

277

TypedDatabaseRouter = object

278

279

class AnyAttrAllowed(Protocol):

280

"""Protocol for classes allowing arbitrary attribute access."""

281

def __getattr__(self, item: str) -> Any: ...

282

def __setattr__(self, item: str, value: Any) -> None: ...

283

284

class RelatedManager(Protocol[_T]):

285

"""Protocol for Django related managers."""

286

pass

287

288

class ManyRelatedManager(Protocol[_T]):

289

"""Protocol for Django many-to-many related managers."""

290

pass

291

```