or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

development-tools.mdextension-setup.mdindex.mdmodels-tables.mdpagination.mdquery-interface.mdsession-management.md

query-interface.mddocs/

0

# Query Interface

1

2

Enhanced query classes with Flask-specific convenience methods for common web patterns like 404 error handling and result processing.

3

4

## Capabilities

5

6

### Query Class

7

8

Enhanced SQLAlchemy Query with Flask convenience methods for web applications.

9

10

```python { .api }

11

class Query(sa_orm.Query):

12

"""

13

SQLAlchemy Query subclass with Flask-specific convenience methods.

14

15

Provides common web application patterns like automatic 404 error

16

handling for missing results. This is the default query class for

17

Model.query properties.

18

19

Note: The query interface is considered legacy in SQLAlchemy.

20

Modern code should prefer session.execute(select()) patterns.

21

"""

22

23

def get_or_404(self, ident: Any, description: str | None = None) -> Any:

24

"""

25

Get record by primary key or abort with 404 Not Found.

26

27

Parameters:

28

- ident: Primary key value to lookup

29

- description: Custom error message for 404 page

30

31

Returns:

32

Model instance matching the primary key

33

34

Raises:

35

404 error if no record found with given primary key

36

"""

37

38

def first_or_404(self, description: str | None = None) -> Any:

39

"""

40

Get first result from query or abort with 404 Not Found.

41

42

Parameters:

43

- description: Custom error message for 404 page

44

45

Returns:

46

First model instance from query results

47

48

Raises:

49

404 error if query returns no results

50

"""

51

52

def one_or_404(self, description: str | None = None) -> Any:

53

"""

54

Get exactly one result from query or abort with 404 Not Found.

55

56

Parameters:

57

- description: Custom error message for 404 page

58

59

Returns:

60

Single model instance from query results

61

62

Raises:

63

404 error if query returns no results or multiple results

64

"""

65

66

def paginate(

67

self,

68

*,

69

page: int | None = None,

70

per_page: int | None = None,

71

max_per_page: int | None = None,

72

error_out: bool = True,

73

count: bool = True,

74

) -> Pagination:

75

"""

76

Apply pagination to the query results.

77

78

Parameters:

79

- page: Current page number (from request args if None)

80

- per_page: Items per page (from request args if None)

81

- max_per_page: Maximum allowed per_page value

82

- error_out: Abort with 404 on invalid page parameters

83

- count: Calculate total item count for pagination metadata

84

85

Returns:

86

Pagination object containing page results and metadata

87

"""

88

```

89

90

### Query Property

91

92

The `_QueryProperty` class that provides the `query` attribute on model classes.

93

94

```python { .api }

95

class _QueryProperty:

96

"""

97

Class property that creates query objects for model classes.

98

99

Internal implementation detail that provides Model.query property.

100

"""

101

102

def __get__(self, obj: Model | None, cls: type[Model]) -> Query:

103

"""

104

Return configured query instance for the model class.

105

106

Parameters:

107

- obj: Model instance (unused)

108

- cls: Model class to create query for

109

110

Returns:

111

Query instance configured for the model class

112

"""

113

```

114

115

## Usage Examples

116

117

### Basic Query Operations

118

119

```python

120

# Model with automatic query property

121

class User(db.Model):

122

id = db.Column(db.Integer, primary_key=True)

123

username = db.Column(db.String(80), unique=True)

124

email = db.Column(db.String(120))

125

126

# Basic querying

127

users = User.query.all()

128

user = User.query.get(1)

129

active_users = User.query.filter_by(active=True).all()

130

```

131

132

### Flask Error Handling

133

134

```python

135

@app.route('/user/<int:user_id>')

136

def show_user(user_id):

137

# Automatically returns 404 if user doesn't exist

138

user = User.query.get_or_404(user_id)

139

return f"User: {user.username}"

140

141

@app.route('/user/first-admin')

142

def first_admin():

143

# Get first admin user or 404 if none exist

144

admin = User.query.filter_by(role='admin').first_or_404()

145

return f"First admin: {admin.username}"

146

147

@app.route('/user/email/<email>')

148

def user_by_email(email):

149

# Expect exactly one user with this email or 404

150

user = User.query.filter_by(email=email).one_or_404()

151

return f"User: {user.username}"

152

```

153

154

### Custom Error Messages

155

156

```python

157

@app.route('/user/<int:user_id>')

158

def show_user(user_id):

159

user = User.query.get_or_404(

160

user_id,

161

description=f"User {user_id} not found in our system"

162

)

163

return f"User: {user.username}"

164

```

165

166

### Query Pagination

167

168

```python

169

@app.route('/users')

170

def list_users():

171

page = request.args.get('page', 1, type=int)

172

173

# Paginate query results

174

pagination = User.query.order_by(User.username).paginate(

175

page=page,

176

per_page=20,

177

max_per_page=100,

178

error_out=False # Don't 404 on invalid page, use page 1

179

)

180

181

return {

182

'users': [u.username for u in pagination.items],

183

'page': pagination.page,

184

'pages': pagination.pages,

185

'has_next': pagination.has_next,

186

'has_prev': pagination.has_prev

187

}

188

```

189

190

### Complex Queries

191

192

```python

193

# Filtering and ordering

194

recent_posts = Post.query.filter(

195

Post.created_at > datetime.utcnow() - timedelta(days=7)

196

).order_by(Post.created_at.desc()).all()

197

198

# Joins and relationships

199

posts_with_authors = Post.query.join(User).filter(

200

User.active == True

201

).order_by(Post.title).all()

202

203

# Aggregation

204

user_post_counts = db.session.query(

205

User.username,

206

db.func.count(Post.id).label('post_count')

207

).join(Post).group_by(User.id).all()

208

```

209

210

### Query Customization

211

212

```python

213

# Custom query class

214

class CustomQuery(Query):

215

def active(self):

216

return self.filter_by(active=True)

217

218

def by_username(self, username):

219

return self.filter_by(username=username)

220

221

# Use custom query class

222

db = SQLAlchemy(app, query_class=CustomQuery)

223

224

class User(db.Model):

225

id = db.Column(db.Integer, primary_key=True)

226

username = db.Column(db.String(80))

227

active = db.Column(db.Boolean, default=True)

228

229

# Now all queries have custom methods

230

active_users = User.query.active().all()

231

user = User.query.by_username('john').first_or_404()

232

```

233

234

### Query with Select (Modern Pattern)

235

236

While the Query interface is available, SQLAlchemy 2.x style is preferred:

237

238

```python

239

# Legacy Query style (still supported)

240

users = User.query.filter_by(active=True).all()

241

242

# Modern select() style (recommended)

243

users = db.session.execute(

244

db.select(User).where(User.active == True)

245

).scalars().all()

246

247

# Both can use Flask conveniences

248

user = db.get_or_404(User, user_id) # Modern

249

user = User.query.get_or_404(user_id) # Legacy

250

```

251

252

### Relationship Queries

253

254

```python

255

class User(db.Model):

256

id = db.Column(db.Integer, primary_key=True)

257

posts = db.relationship('Post', backref='author', lazy='dynamic')

258

259

class Post(db.Model):

260

id = db.Column(db.Integer, primary_key=True)

261

user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

262

263

# Query through relationships

264

user = User.query.get(1)

265

user_posts = user.posts.filter_by(published=True).all()

266

267

# Backref queries

268

post = Post.query.get(1)

269

post_author = post.author # Automatically loaded

270

```

271

272

## Performance Considerations

273

274

- **Query Interface Legacy**: The Query interface is considered legacy in SQLAlchemy 2.x

275

- **Prefer Select**: Use `db.session.execute(db.select())` for new code

276

- **Query Caching**: Consider query result caching for frequently accessed data

277

- **Eager Loading**: Use `joinedload()` or `selectinload()` to avoid N+1 queries

278

- **Pagination**: Always paginate large result sets to avoid memory issues