Skip to content

Commit 884501f

Browse files
Merge pull request #2535 from redis/DOC-6089-list-notebooks
DOC-6089 list notebooks
2 parents a4abc2d + 8059a20 commit 884501f

File tree

10 files changed

+2246
-13
lines changed

10 files changed

+2246
-13
lines changed

build/jupyterize/SPECIFICATION.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,8 @@ Go has a unique requirement: notebooks MUST have a `func main() {}` wrapper for
12551255
- This ensures imports and `func main() {}` are in the same cell, matching gophernotes expectations
12561256
- The wrapper is injected as boilerplate, then removed from source, then re-injected
12571257
- This ensures the notebook has a clean wrapper without test framework code
1258+
- **Client connection configuration**: Boilerplate can include more than just wrappersit can include client connection setup (e.g., Redis client initialization with configuration options like `MaintNotificationsConfig`)
1259+
- This allows notebooks to have a working client connection without requiring users to add boilerplate code manually
12581260

12591261
**Pattern complexity comparison** (As Proposed):
12601262
- **C#**: 5 patterns (class/method wrappers + closing braces)
@@ -1363,6 +1365,53 @@ When adding a new language, ask:
13631365

13641366
If yes to any of these, use Strategy 2 (append to first cell). Otherwise, use Strategy 1 (separate cell).
13651367

1368+
### Boilerplate Content Patterns (What Goes in Boilerplate)
1369+
1370+
**Lesson Learned**: Boilerplate is not limited to language wrappers and imports. It can include any code that should appear in every notebook generated from that language, including client connection setup and configuration.
1371+
1372+
**Common boilerplate content**:
1373+
1. **Language wrappers** (required by kernel)
1374+
- C#: NuGet package directives (`#r "nuget: ..."`)
1375+
- Go: `func main() {}` wrapper (gophernotes requirement)
1376+
- Java: (typically empty; dependencies handled via `%maven` magic commands)
1377+
1378+
2. **Client connection setup** (optional but recommended)
1379+
- Redis client initialization with connection options
1380+
- Configuration flags (e.g., `MaintNotificationsConfig` for Go)
1381+
- Default connection parameters (host, port, password)
1382+
- This ensures notebooks have a working client without manual setup
1383+
1384+
3. **Import statements** (language-dependent)
1385+
- C#: NuGet directives (not traditional imports)
1386+
- Go: Typically in STEP blocks, not boilerplate (unless needed for wrapper)
1387+
- Java: Typically in STEP blocks, not boilerplate
1388+
1389+
**Example: Go with Client Connection**:
1390+
```json
1391+
{
1392+
"go": {
1393+
"boilerplate": [
1394+
"rdb := redis.NewClient(&redis.Options{",
1395+
"\tAddr: \"localhost:6379\",",
1396+
"\tPassword: \"\",",
1397+
"\tDB: 0,",
1398+
"\tMaintNotificationsConfig: &maintnotifications.Config{",
1399+
"\t\tMode: maintnotifications.ModeDisabled,",
1400+
"\t},",
1401+
"})",
1402+
"func main() {}"
1403+
]
1404+
}
1405+
}
1406+
```
1407+
1408+
**Design considerations**:
1409+
- **Boilerplate should be minimal**: Include only code that's needed in every notebook
1410+
- **Avoid language-specific details**: Don't include code that only applies to some examples
1411+
- **Configuration over code**: Use boilerplate for configuration (connection options) rather than business logic
1412+
- **Test with real examples**: Verify boilerplate works with actual example files before committing
1413+
- **Document assumptions**: If boilerplate assumes certain imports or packages, document this in comments or README
1414+
13661415

13671416
### Testing Checklist (Language-Specific)
13681417

build/jupyterize/jupyterize_config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@
4545
},
4646
"go": {
4747
"boilerplate": [
48+
"rdb := redis.NewClient(&redis.Options{",
49+
"\tAddr: \"localhost:6379\",",
50+
"\tPassword: \"\",",
51+
"\tDB: 0,",
52+
"\tMaintNotificationsConfig: &maintnotifications.Config{",
53+
"\t\tMode: maintnotifications.ModeDisabled,",
54+
"\t},",
55+
"})",
4856
"func main() {}"
4957
],
5058
"unwrap_patterns": [

build/jupyterize/test_jupyterize.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ def test_python_no_boilerplate():
516516

517517

518518
def test_go_boilerplate_injection():
519-
"""Test Go boilerplate injection (func main() {} appended to first cell)."""
519+
"""Test Go boilerplate injection (client config and func main() {} appended to first cell)."""
520520
print("\nTesting Go boilerplate injection...")
521521

522522
# Create test file with Go code
@@ -556,12 +556,16 @@ def test_go_boilerplate_injection():
556556
assert nb['metadata']['kernelspec']['name'] == 'gophernotes', \
557557
f"Kernel should be gophernotes, got {nb['metadata']['kernelspec']['name']}"
558558

559-
# First cell should contain imports AND func main() {}
559+
# First cell should contain imports, client config, and func main() {}
560560
# (boilerplate is appended to first cell for Go, not separate)
561561
first_cell = nb['cells'][0]
562562
first_cell_source = ''.join(first_cell['source'])
563563
assert 'import (' in first_cell_source, \
564564
f"First cell should contain imports, got: {first_cell_source}"
565+
assert 'redis.NewClient' in first_cell_source, \
566+
f"First cell should contain redis.NewClient, got: {first_cell_source}"
567+
assert 'MaintNotificationsConfig' in first_cell_source, \
568+
f"First cell should contain MaintNotificationsConfig, got: {first_cell_source}"
565569
assert 'func main() {}' in first_cell_source, \
566570
f"First cell should contain 'func main() {{}}', got: {first_cell_source}"
567571

content/develop/data-types/lists.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ See the [complete series of list commands]({{< relref "/commands/" >}}?group=lis
9292

9393
* To limit the length of a list you can call [`LTRIM`]({{< relref "/commands/ltrim" >}}):
9494
{{< clients-example list_tutorial ltrim.1 >}}
95+
> DEL bikes:repairs
96+
(integer) 1
9597
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
9698
(integer) 5
9799
> LTRIM bikes:repairs 0 2
@@ -103,6 +105,7 @@ OK
103105
{{< /clients-example >}}
104106

105107
### What are Lists?
108+
106109
To explain the List data type it's better to start with a little bit of theory,
107110
as the term *List* is often used in an improper way by information technology
108111
folks. For instance "Python Lists" are not what the name may suggest (Linked
@@ -143,6 +146,8 @@ element into a list, on the right (at the tail). Finally the
143146
[`LRANGE`]({{< relref "/commands/lrange" >}}) command extracts ranges of elements from lists:
144147

145148
{{< clients-example list_tutorial lpush_rpush >}}
149+
> DEL bikes:repairs
150+
(integer) 1
146151
> RPUSH bikes:repairs bike:1
147152
(integer) 1
148153
> RPUSH bikes:repairs bike:2
@@ -167,6 +172,8 @@ Both commands are *variadic commands*, meaning that you are free to push
167172
multiple elements into a list in a single call:
168173

169174
{{< clients-example list_tutorial variadic >}}
175+
> DEL bikes:repairs
176+
(integer) 1
170177
> RPUSH bikes:repairs bike:1 bike:2 bike:3
171178
(integer) 3
172179
> LPUSH bikes:repairs bike:important_bike bike:very_important_bike
@@ -187,6 +194,8 @@ sequence of commands the list is empty and there are no more elements to
187194
pop:
188195

189196
{{< clients-example list_tutorial lpop_rpop >}}
197+
> DEL bikes:repairs
198+
(integer) 1
190199
> RPUSH bikes:repairs bike:1 bike:2 bike:3
191200
(integer) 3
192201
> RPOP bikes:repairs
@@ -239,6 +248,8 @@ For example, if you're adding bikes on the end of a list of repairs, but only
239248
want to worry about the 3 that have been on the list the longest:
240249

241250
{{< clients-example list_tutorial ltrim >}}
251+
> DEL bikes:repairs
252+
(integer) 1
242253
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
243254
(integer) 5
244255
> LTRIM bikes:repairs 0 2
@@ -256,6 +267,8 @@ to add a new element and discard elements exceeding a limit. Using
256267
[`LTRIM`]({{< relref "/commands/ltrim" >}}) with negative indexes can then be used to keep only the 3 most recently added:
257268

258269
{{< clients-example list_tutorial ltrim_end_of_list >}}
270+
> DEL bikes:repairs
271+
(integer) 1
259272
> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
260273
(integer) 5
261274
> LTRIM bikes:repairs -3 -1
@@ -273,8 +286,7 @@ without any need to remember very old data.
273286
Note: while [`LRANGE`]({{< relref "/commands/lrange" >}}) is technically an O(N) command, accessing small ranges
274287
towards the head or the tail of the list is a constant time operation.
275288

276-
Blocking operations on lists
277-
---
289+
## Blocking operations on lists
278290

279291
Lists have a special feature that make them suitable to implement queues,
280292
and in general as a building block for inter process communication systems:
@@ -304,6 +316,8 @@ timeout is reached.
304316
This is an example of a [`BRPOP`]({{< relref "/commands/brpop" >}}) call we could use in the worker:
305317

306318
{{< clients-example list_tutorial brpop >}}
319+
> DEL bikes:repairs
320+
(integer) 1
307321
> RPUSH bikes:repairs bike:1 bike:2
308322
(integer) 2
309323
> BRPOP bikes:repairs 1
@@ -366,6 +380,8 @@ Examples of rule 1:
366380
However we can't perform operations against the wrong type if the key exists:
367381

368382
{{< clients-example list_tutorial rule_1.1 >}}
383+
> DEL new_bikes
384+
(integer) 1
369385
> SET new_bikes bike:1
370386
OK
371387
> TYPE new_bikes
@@ -377,6 +393,8 @@ string
377393
Example of rule 2:
378394

379395
{{< clients-example list_tutorial rule_2 >}}
396+
> DEL bikes:repairs
397+
(integer) 1
380398
> LPUSH bikes:repairs bike:1 bike:2 bike:3
381399
(integer) 3
382400
> EXISTS bikes:repairs
@@ -407,8 +425,7 @@ Example of rule 3:
407425

408426
## Limits
409427

410-
The max length of a Redis list is 2^32 - 1 (4,294,967,295) elements.
411-
428+
The maximum length of a Redis list is 2^32 - 1 (4,294,967,295) elements.
412429

413430
## Performance
414431

local_examples/php/DtListTest.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// EXAMPLE: list_tutorial
2+
// BINDER_ID php-dt-list
23
<?php
34

45
require 'vendor/autoload.php';
@@ -106,10 +107,11 @@ public function testDtList() {
106107
$this->assertEquals('bike:2', $res12);
107108
$this->assertEquals(['bike:1'], $res13);
108109
$this->assertEquals(['bike:2'], $res14);
109-
$r->del('bikes:repairs');
110110
// REMOVE_END
111111

112112
// STEP_START lpush_rpush
113+
$r->del('bikes:repairs');
114+
113115
$res15 = $r->rpush('bikes:repairs', 'bike:1');
114116
echo $res15 . PHP_EOL;
115117
// >>> 1
@@ -131,10 +133,11 @@ public function testDtList() {
131133
$this->assertEquals(2, $res16);
132134
$this->assertEquals(3, $res17);
133135
$this->assertEquals(['bike:important_bike', 'bike:1', 'bike:2'], $res18);
134-
$r->del('bikes:repairs');
135136
// REMOVE_END
136137

137138
// STEP_START variadic
139+
$r->del('bikes:repairs');
140+
138141
$res19 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3');
139142
echo $res19 . PHP_EOL;
140143
// >>> 3
@@ -157,10 +160,11 @@ public function testDtList() {
157160
'bike:2',
158161
'bike:3',
159162
], $res21);
160-
$r->del('bikes:repairs');
161163
// REMOVE_END
162164

163165
// STEP_START lpop_rpop
166+
$r->del('bikes:repairs');
167+
164168
$res22 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3');
165169
echo $res22 . PHP_EOL;
166170
// >>> 3
@@ -206,10 +210,11 @@ public function testDtList() {
206210
$this->assertEquals(5, $res27);
207211
$this->assertEquals('OK', $res28);
208212
$this->assertEquals(['bike:1', 'bike:2', 'bike:3'], $res29);
209-
$r->del('bikes:repairs');
210213
// REMOVE_END
211214

212215
// STEP_START ltrim_end_of_list
216+
$r->del('bikes:repairs');
217+
213218
$res27 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3', 'bike:4', 'bike:5');
214219
echo $res27 . PHP_EOL;
215220
// >>> 5
@@ -226,10 +231,11 @@ public function testDtList() {
226231
$this->assertEquals(5, $res27);
227232
$this->assertEquals('OK', $res28);
228233
$this->assertEquals(['bike:3', 'bike:4', 'bike:5'], $res29);
229-
$r->del('bikes:repairs');
230234
// REMOVE_END
231235

232236
// STEP_START brpop
237+
$r->del('bikes:repairs');
238+
233239
$res31 = $r->rpush('bikes:repairs', 'bike:1', 'bike:2');
234240
echo $res31 . PHP_EOL;
235241
// >>> 2
@@ -267,10 +273,11 @@ public function testDtList() {
267273
// REMOVE_START
268274
$this->assertEquals(0, $res35);
269275
$this->assertEquals(3, $res36);
270-
$r->del('new_bikes');
271276
// REMOVE_END
272277

273278
// STEP_START rule_1.1
279+
$r->del('new_bikes');
280+
274281
$res37 = $r->set('new_bikes', 'bike:1');
275282
echo $res37 . PHP_EOL;
276283
// >>> True
@@ -297,10 +304,10 @@ public function testDtList() {
297304
} catch (\Predis\Response\ServerException $e) {
298305
$this->assertStringContainsString('wrong kind of value', strtolower($e->getMessage()));
299306
}
300-
$r->del('new_bikes');
301307
// REMOVE_END
302308

303309
// STEP_START rule_2
310+
$r->del('bikes:repairs');
304311
$res36 = $r->lpush('bikes:repairs', 'bike:1', 'bike:2', 'bike:3');
305312
echo $res36 . PHP_EOL;
306313
// >>> 3

0 commit comments

Comments
 (0)