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

pagination.mddocs/

0

# Pagination

1

2

Built-in pagination support for both modern SQLAlchemy select statements and legacy Query objects, with request parameter integration and pagination widget helpers.

3

4

## Capabilities

5

6

### Pagination Base Class

7

8

Base class for paginating query results with page navigation and metadata.

9

10

```python { .api }

11

class Pagination:

12

"""

13

Base pagination class for managing paged query results.

14

15

Provides page navigation, item access, and metadata for building

16

pagination widgets in web applications. Don't create instances

17

manually - use db.paginate() or query.paginate() instead.

18

"""

19

20

def __init__(

21

self,

22

page: int | None = None,

23

per_page: int | None = None,

24

max_per_page: int | None = 100,

25

error_out: bool = True,

26

count: bool = True,

27

**kwargs: Any,

28

) -> None:

29

"""

30

Initialize pagination object.

31

32

Parameters:

33

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

34

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

35

- max_per_page: Maximum allowed per_page value

36

- error_out: Abort with 404 on invalid parameters

37

- count: Calculate total item count

38

- kwargs: Query-specific arguments for subclasses

39

"""

40

41

# Properties for current page state

42

page: int # Current page number

43

per_page: int # Items per page

44

max_per_page: int | None # Maximum allowed per_page

45

items: list[Any] # Items on current page

46

total: int | None # Total items across all pages

47

48

@property

49

def first(self) -> int:

50

"""Number of first item on page (1-based) or 0 if no items."""

51

52

@property

53

def last(self) -> int:

54

"""Number of last item on page (1-based) or 0 if no items."""

55

56

@property

57

def pages(self) -> int:

58

"""Total number of pages."""

59

60

@property

61

def has_prev(self) -> bool:

62

"""True if this is not the first page."""

63

64

@property

65

def prev_num(self) -> int | None:

66

"""Previous page number or None if first page."""

67

68

def prev(self, *, error_out: bool = False) -> Pagination:

69

"""

70

Get Pagination object for previous page.

71

72

Parameters:

73

- error_out: Abort with 404 on invalid page

74

75

Returns:

76

Pagination object for previous page

77

"""

78

79

@property

80

def has_next(self) -> bool:

81

"""True if this is not the last page."""

82

83

@property

84

def next_num(self) -> int | None:

85

"""Next page number or None if last page."""

86

87

def next(self, *, error_out: bool = False) -> Pagination:

88

"""

89

Get Pagination object for next page.

90

91

Parameters:

92

- error_out: Abort with 404 on invalid page

93

94

Returns:

95

Pagination object for next page

96

"""

97

98

def iter_pages(

99

self,

100

*,

101

left_edge: int = 2,

102

left_current: int = 2,

103

right_current: int = 4,

104

right_edge: int = 2,

105

) -> Iterator[int | None]:

106

"""

107

Yield page numbers for pagination widget with smart truncation.

108

109

Parameters:

110

- left_edge: Pages to show from start

111

- left_current: Pages to show left of current

112

- right_current: Pages to show right of current

113

- right_edge: Pages to show from end

114

115

Yields:

116

Page numbers or None for gaps (e.g., 1, 2, None, 7, 8, 9, None, 19, 20)

117

"""

118

119

def __iter__(self) -> Iterator[Any]:

120

"""Iterate over items on current page."""

121

```

122

123

### Select Statement Pagination

124

125

Pagination implementation for modern SQLAlchemy select statements.

126

127

```python { .api }

128

class SelectPagination(Pagination):

129

"""

130

Pagination for SQLAlchemy select statements.

131

132

Used by db.paginate() for modern SQLAlchemy 2.x select() queries.

133

Returned by SQLAlchemy.paginate() method.

134

"""

135

136

def _query_items(self) -> list[Any]:

137

"""Execute paginated select query to get items for current page."""

138

139

def _query_count(self) -> int:

140

"""Execute count query to get total number of items."""

141

```

142

143

### Query Pagination

144

145

Pagination implementation for legacy Query objects.

146

147

```python { .api }

148

class QueryPagination(Pagination):

149

"""

150

Pagination for legacy SQLAlchemy Query objects.

151

152

Used by Query.paginate() for SQLAlchemy 1.x style queries.

153

Returned by Query.paginate() method.

154

"""

155

156

def _query_items(self) -> list[Any]:

157

"""Execute paginated query to get items for current page."""

158

159

def _query_count(self) -> int:

160

"""Execute count query to get total number of items."""

161

```

162

163

## Usage Examples

164

165

### Basic Pagination with Select Statements

166

167

```python

168

@app.route('/users')

169

def list_users():

170

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

171

172

# Modern SQLAlchemy 2.x style pagination

173

pagination = db.paginate(

174

db.select(User).order_by(User.username),

175

page=page,

176

per_page=20,

177

max_per_page=100

178

)

179

180

return render_template('users.html', pagination=pagination)

181

```

182

183

### Pagination with Query Objects

184

185

```python

186

@app.route('/posts')

187

def list_posts():

188

# Legacy Query style pagination

189

pagination = Post.query.filter_by(published=True).order_by(

190

Post.created_at.desc()

191

).paginate(

192

per_page=10,

193

error_out=False # Use page 1 instead of 404 for invalid pages

194

)

195

196

return render_template('posts.html', pagination=pagination)

197

```

198

199

### Request Parameter Integration

200

201

```python

202

@app.route('/search')

203

def search():

204

query = request.args.get('q', '')

205

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

206

per_page = min(request.args.get('per_page', 20, type=int), 100)

207

208

# Pagination automatically reads 'page' and 'per_page' from request

209

# if not explicitly provided

210

pagination = db.paginate(

211

db.select(Post).where(Post.title.contains(query)),

212

page=page,

213

per_page=per_page

214

)

215

216

return {

217

'results': [post.title for post in pagination.items],

218

'pagination': {

219

'page': pagination.page,

220

'pages': pagination.pages,

221

'per_page': pagination.per_page,

222

'total': pagination.total,

223

'has_next': pagination.has_next,

224

'has_prev': pagination.has_prev

225

}

226

}

227

```

228

229

### Pagination Templates

230

231

```html

232

<!-- Example Jinja2 template for pagination -->

233

<div class="pagination">

234

{% if pagination.has_prev %}

235

<a href="{{ url_for(request.endpoint, page=pagination.prev_num, **request.args) }}">&laquo; Prev</a>

236

{% endif %}

237

238

{% for page in pagination.iter_pages() %}

239

{% if page %}

240

{% if page != pagination.page %}

241

<a href="{{ url_for(request.endpoint, page=page, **request.args) }}">{{ page }}</a>

242

{% else %}

243

<strong>{{ page }}</strong>

244

{% endif %}

245

{% else %}

246

<span>…</span>

247

{% endif %}

248

{% endfor %}

249

250

{% if pagination.has_next %}

251

<a href="{{ url_for(request.endpoint, page=pagination.next_num, **request.args) }}">Next &raquo;</a>

252

{% endif %}

253

</div>

254

255

<!-- Display current page items -->

256

<div class="items">

257

{% for item in pagination.items %}

258

<div>{{ item.title }}</div>

259

{% endfor %}

260

</div>

261

262

<!-- Pagination info -->

263

<div class="info">

264

Showing {{ pagination.first }} to {{ pagination.last }} of {{ pagination.total }} items

265

(Page {{ pagination.page }} of {{ pagination.pages }})

266

</div>

267

```

268

269

### Advanced Pagination Controls

270

271

```python

272

@app.route('/products')

273

def products():

274

category = request.args.get('category')

275

sort = request.args.get('sort', 'name')

276

277

query = db.select(Product)

278

279

if category:

280

query = query.where(Product.category == category)

281

282

if sort == 'price':

283

query = query.order_by(Product.price)

284

else:

285

query = query.order_by(Product.name)

286

287

pagination = db.paginate(

288

query,

289

per_page=12,

290

max_per_page=50,

291

count=True # Calculate total for "X of Y" display

292

)

293

294

return render_template('products.html',

295

pagination=pagination,

296

category=category,

297

sort=sort)

298

```

299

300

### Pagination Widget Helper

301

302

```python

303

def generate_page_links(pagination, endpoint, **kwargs):

304

"""Generate pagination links for templates."""

305

links = []

306

307

# Previous page

308

if pagination.has_prev:

309

links.append({

310

'url': url_for(endpoint, page=pagination.prev_num, **kwargs),

311

'text': 'Previous',

312

'current': False

313

})

314

315

# Page numbers with smart truncation

316

for page in pagination.iter_pages():

317

if page is None:

318

links.append({'text': '…', 'current': False})

319

else:

320

links.append({

321

'url': url_for(endpoint, page=page, **kwargs),

322

'text': str(page),

323

'current': page == pagination.page

324

})

325

326

# Next page

327

if pagination.has_next:

328

links.append({

329

'url': url_for(endpoint, page=pagination.next_num, **kwargs),

330

'text': 'Next',

331

'current': False

332

})

333

334

return links

335

```

336

337

### Performance Optimization

338

339

```python

340

@app.route('/users')

341

def list_users():

342

# Disable count query for better performance on large tables

343

pagination = db.paginate(

344

db.select(User).order_by(User.created_at.desc()),

345

per_page=25,

346

count=False # Skip expensive COUNT query

347

)

348

349

# pagination.total will be None

350

# Use has_next/has_prev for navigation instead

351

return render_template('users.html', pagination=pagination)

352

```

353

354

### Custom Pagination Parameters

355

356

```python

357

def custom_paginate(query, page=None, per_page=None):

358

"""Custom pagination with different defaults."""

359

return db.paginate(

360

query,

361

page=page or 1,

362

per_page=per_page or 50, # Higher default

363

max_per_page=200, # Higher maximum

364

error_out=False # Don't 404 on invalid pages

365

)

366

367

@app.route('/items')

368

def list_items():

369

pagination = custom_paginate(

370

db.select(Item).order_by(Item.name),

371

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

372

)

373

return render_template('items.html', pagination=pagination)

374

```

375

376

## Pagination Properties Reference

377

378

- **page**: Current page number (1-based)

379

- **per_page**: Number of items per page

380

- **total**: Total number of items (None if count=False)

381

- **items**: List of items on current page

382

- **pages**: Total number of pages

383

- **first**: First item number on page (1-based)

384

- **last**: Last item number on page (1-based)

385

- **has_prev**: True if previous page exists

386

- **has_next**: True if next page exists

387

- **prev_num**: Previous page number (None if first page)

388

- **next_num**: Next page number (None if last page)