0
# Session Management
1
2
Session handling, user input/output management, and session utilities for managing application state and user interactions. Each user session provides isolated state and communication between client and server.
3
4
## Capabilities
5
6
### Session Class
7
8
The main session object that represents a user's connection to the Shiny application.
9
10
```python { .api }
11
class Session:
12
"""
13
Represents a user session in a Shiny application.
14
"""
15
def __init__(self, **kwargs: object) -> None: ...
16
17
def send_custom_message(
18
self,
19
type: str,
20
message: dict[str, object]
21
) -> None:
22
"""
23
Send a custom message to the client.
24
25
Args:
26
type: Message type identifier.
27
message: Message data to send.
28
"""
29
30
def send_input_message(
31
self,
32
id: str,
33
message: dict[str, object]
34
) -> None:
35
"""
36
Send a message to a specific input.
37
38
Args:
39
id: Input identifier.
40
message: Message data to send.
41
"""
42
43
def download(
44
self,
45
id: str,
46
filename: str,
47
media_type: str = "application/octet-stream"
48
) -> None:
49
"""
50
Trigger a file download.
51
52
Args:
53
id: Download handler identifier.
54
filename: Name of file to download.
55
media_type: MIME type of the file.
56
"""
57
58
@property
59
def client_data(self) -> ClientData:
60
"""Access to client-side data and information."""
61
62
@property
63
def user_info(self) -> dict[str, object] | None:
64
"""User information if available."""
65
66
@property
67
def groups(self) -> list[str] | None:
68
"""User groups if available."""
69
```
70
71
#### Usage Examples
72
73
```python
74
def server(input: Inputs, output: Outputs, session: Session):
75
# Send custom messages to client JavaScript
76
@reactive.effect
77
@reactive.event(input.notify_button)
78
def send_notification():
79
session.send_custom_message(
80
"notification",
81
{
82
"message": "Processing complete!",
83
"type": "success",
84
"duration": 3000
85
}
86
)
87
88
# Trigger downloads
89
@reactive.effect
90
@reactive.event(input.download_trigger)
91
def trigger_download():
92
session.download(
93
"data_download",
94
filename=f"export_{datetime.now().strftime('%Y%m%d')}.csv",
95
media_type="text/csv"
96
)
97
98
# Send messages to specific inputs
99
@reactive.effect
100
def update_input_config():
101
if input.advanced_mode():
102
session.send_input_message(
103
"parameters",
104
{"enable_advanced": True, "show_tooltips": True}
105
)
106
```
107
108
### Inputs Class
109
110
Provides access to user input values in a reactive context.
111
112
```python { .api }
113
class Inputs:
114
"""
115
Reactive access to user input values.
116
"""
117
def __call__(self) -> dict[str, object]:
118
"""
119
Get all input values as a dictionary.
120
121
Returns:
122
Dictionary of all current input values.
123
"""
124
125
def __getattr__(self, name: str) -> Callable[[], object]:
126
"""
127
Get a specific input value as a reactive function.
128
129
Args:
130
name: Input identifier.
131
132
Returns:
133
Function that returns the current input value.
134
"""
135
136
def __contains__(self, name: str) -> bool:
137
"""
138
Check if an input exists.
139
140
Args:
141
name: Input identifier.
142
143
Returns:
144
True if input exists.
145
"""
146
147
def __iter__(self) -> Iterator[str]:
148
"""
149
Iterate over input names.
150
151
Returns:
152
Iterator of input identifiers.
153
"""
154
```
155
156
#### Usage Examples
157
158
```python
159
def server(input: Inputs, output: Outputs, session: Session):
160
# Access individual inputs reactively
161
@output
162
@render.text
163
def greeting():
164
name = input.user_name() # Reactive - updates when input changes
165
return f"Hello, {name}!"
166
167
# Check if inputs exist
168
@reactive.calc
169
def processed_data():
170
if "optional_filter" in input:
171
filter_value = input.optional_filter()
172
return apply_filter(base_data(), filter_value)
173
return base_data()
174
175
# Get all inputs at once
176
@output
177
@render.text
178
def debug_info():
179
all_inputs = input() # Get all input values
180
return f"Current inputs: {all_inputs}"
181
182
# Iterate over inputs
183
@reactive.calc
184
def input_summary():
185
summary = {}
186
for input_id in input:
187
value = getattr(input, input_id)()
188
summary[input_id] = type(value).__name__
189
return summary
190
191
# Conditional logic based on inputs
192
@reactive.calc
193
def analysis_config():
194
config = {"base_analysis": True}
195
196
if "advanced_options" in input and input.advanced_options():
197
config["detailed_stats"] = True
198
config["confidence_intervals"] = True
199
200
if "experimental_features" in input:
201
config["experimental"] = input.experimental_features()
202
203
return config
204
```
205
206
### Outputs Class
207
208
Container for managing rendered outputs that get sent to the client.
209
210
```python { .api }
211
class Outputs:
212
"""
213
Container for application outputs.
214
"""
215
def __setattr__(
216
self,
217
name: str,
218
value: OutputRenderer[object]
219
) -> None:
220
"""
221
Assign an output renderer to an output ID.
222
223
Args:
224
name: Output identifier.
225
value: Output renderer function.
226
"""
227
228
def __getattr__(self, name: str) -> OutputRenderer[object]:
229
"""
230
Get an output renderer by name.
231
232
Args:
233
name: Output identifier.
234
235
Returns:
236
Output renderer if it exists.
237
"""
238
239
def __contains__(self, name: str) -> bool:
240
"""
241
Check if an output exists.
242
243
Args:
244
name: Output identifier.
245
246
Returns:
247
True if output exists.
248
"""
249
```
250
251
#### Usage Examples
252
253
```python
254
def server(input: Inputs, output: Outputs, session: Session):
255
# Standard output assignment using decorators
256
@output
257
@render.text
258
def status_message():
259
return "Application ready"
260
261
# Dynamic output assignment
262
def create_plot_output():
263
@render.plot
264
def dynamic_plot():
265
return generate_plot(input.plot_data())
266
return dynamic_plot
267
268
@reactive.effect
269
def setup_outputs():
270
if input.enable_plotting():
271
output.main_plot = create_plot_output()
272
273
# Conditional output rendering
274
@output
275
@render.ui
276
def conditional_output():
277
if "results" in output:
278
return ui.div(
279
ui.h3("Results Available"),
280
ui.output_table("results")
281
)
282
return ui.p("No results yet")
283
284
# Multiple outputs with shared computation
285
@reactive.calc
286
def analysis_results():
287
return perform_analysis(input.dataset())
288
289
@output
290
@render.table
291
def results_table():
292
results = analysis_results()
293
return results["summary_table"]
294
295
@output
296
@render.plot
297
def results_plot():
298
results = analysis_results()
299
return results["visualization"]
300
```
301
302
### Client Data
303
304
Access to client-side information and browser data.
305
306
```python { .api }
307
class ClientData:
308
"""
309
Access to client-side data and browser information.
310
"""
311
def __call__(self) -> dict[str, object]:
312
"""
313
Get all client data as a dictionary.
314
315
Returns:
316
Dictionary of client-side information.
317
"""
318
319
def __getattr__(self, name: str) -> Callable[[], object]:
320
"""
321
Get specific client data reactively.
322
323
Args:
324
name: Client data property name.
325
326
Returns:
327
Function that returns the client data value.
328
"""
329
330
@property
331
def url_protocol(self) -> str:
332
"""Protocol used (http or https)."""
333
334
@property
335
def url_hostname(self) -> str:
336
"""Hostname of the request."""
337
338
@property
339
def url_port(self) -> int | None:
340
"""Port number if specified."""
341
342
@property
343
def url_pathname(self) -> str:
344
"""URL pathname."""
345
346
@property
347
def url_search(self) -> str:
348
"""URL search parameters."""
349
350
@property
351
def pixelratio(self) -> float:
352
"""Device pixel ratio."""
353
```
354
355
#### Usage Examples
356
357
```python
358
def server(input: Inputs, output: Outputs, session: Session):
359
# Access client information
360
@output
361
@render.text
362
def client_info():
363
client = session.client_data
364
return f"""
365
Browser Info:
366
- URL: {client.url_protocol}://{client.url_hostname}:{client.url_port}{client.url_pathname}
367
- Search: {client.url_search}
368
- Pixel Ratio: {client.pixelratio}
369
"""
370
371
# Responsive behavior based on client data
372
@reactive.calc
373
def plot_dimensions():
374
client = session.client_data
375
pixel_ratio = client.pixelratio
376
377
# Adjust plot size for high-DPI displays
378
base_width = 800
379
base_height = 600
380
381
return {
382
"width": base_width * pixel_ratio,
383
"height": base_height * pixel_ratio,
384
"dpi": 96 * pixel_ratio
385
}
386
387
@output
388
@render.plot
389
def responsive_plot():
390
dims = plot_dimensions()
391
fig = create_plot(input.data())
392
fig.set_size_inches(dims["width"]/dims["dpi"], dims["height"]/dims["dpi"])
393
return fig
394
395
# URL-based routing
396
@reactive.calc
397
def current_page():
398
pathname = session.client_data.url_pathname
399
if pathname.endswith("/analysis"):
400
return "analysis"
401
elif pathname.endswith("/data"):
402
return "data"
403
else:
404
return "home"
405
406
@output
407
@render.ui
408
def page_content():
409
page = current_page()
410
411
if page == "analysis":
412
return analysis_page_ui()
413
elif page == "data":
414
return data_page_ui()
415
else:
416
return home_page_ui()
417
```
418
419
### Session Context Management
420
421
Utilities for managing session context and lifecycle.
422
423
```python { .api }
424
def get_current_session() -> Session | None:
425
"""
426
Get the current session if running within a session context.
427
428
Returns:
429
Current session or None if not in session context.
430
"""
431
432
def require_active_session(what: str | None = None) -> Session:
433
"""
434
Require that there is an active session context.
435
436
Args:
437
what: Description of what requires an active session.
438
439
Returns:
440
Current session.
441
442
Raises:
443
RuntimeError: If no active session.
444
"""
445
446
def session_context(session: Session) -> ContextManager[None]:
447
"""
448
Create a session context manager.
449
450
Args:
451
session: Session to use as context.
452
453
Returns:
454
Context manager that sets the session context.
455
"""
456
```
457
458
#### Usage Examples
459
460
```python
461
from shiny.session import get_current_session, require_active_session, session_context
462
463
# Utility functions that work with sessions
464
def send_status_update(message: str, status_type: str = "info"):
465
"""Send a status update to the current session."""
466
session = get_current_session()
467
if session:
468
session.send_custom_message("status_update", {
469
"message": message,
470
"type": status_type,
471
"timestamp": datetime.now().isoformat()
472
})
473
474
def log_user_action(action: str, details: dict[str, object] | None = None):
475
"""Log a user action with session context."""
476
session = require_active_session("log user action")
477
478
log_entry = {
479
"session_id": id(session),
480
"action": action,
481
"timestamp": datetime.now().isoformat(),
482
"details": details or {}
483
}
484
485
# Log to database or file
486
logger.info(f"User action: {log_entry}")
487
488
# Background task with session context
489
async def process_data_async(session: Session, data: pd.DataFrame):
490
"""Process data asynchronously with session context."""
491
with session_context(session):
492
# Send progress updates
493
send_status_update("Starting data processing...", "info")
494
495
# Simulate long-running task
496
for i in range(10):
497
await asyncio.sleep(1)
498
progress = (i + 1) * 10
499
send_status_update(f"Processing... {progress}% complete", "info")
500
501
# Final update
502
send_status_update("Data processing complete!", "success")
503
504
return processed_data
505
506
# Usage in server function
507
def server(input: Inputs, output: Outputs, session: Session):
508
509
@reactive.effect
510
@reactive.event(input.process_button)
511
def start_processing():
512
data = input.uploaded_data()
513
514
# Log the action
515
log_user_action("start_data_processing", {
516
"data_rows": len(data),
517
"data_columns": len(data.columns)
518
})
519
520
# Start async processing
521
asyncio.create_task(process_data_async(session, data))
522
523
@reactive.effect
524
def track_input_changes():
525
# This runs whenever any input changes
526
current_inputs = input()
527
log_user_action("input_changed", {
528
"changed_inputs": list(current_inputs.keys()),
529
"input_count": len(current_inputs)
530
})
531
```
532
533
### Session Lifecycle Events
534
535
Hooks for managing session startup and cleanup.
536
537
```python { .api }
538
def on_session_start(fn: Callable[[Session], None]) -> None:
539
"""
540
Register a callback for when sessions start.
541
542
Args:
543
fn: Callback function that takes a Session.
544
"""
545
546
def on_session_end(fn: Callable[[Session], None]) -> None:
547
"""
548
Register a callback for when sessions end.
549
550
Args:
551
fn: Callback function that takes a Session.
552
"""
553
```
554
555
#### Usage Examples
556
557
```python
558
# Session lifecycle management
559
def initialize_user_session(session: Session):
560
"""Initialize user session with defaults."""
561
session_id = id(session)
562
563
# Initialize session data
564
session_data[session_id] = {
565
"start_time": datetime.now(),
566
"user_preferences": load_default_preferences(),
567
"temporary_files": []
568
}
569
570
# Send welcome message
571
session.send_custom_message("welcome", {
572
"message": "Welcome to the application!",
573
"session_id": session_id
574
})
575
576
logger.info(f"Session {session_id} started")
577
578
def cleanup_user_session(session: Session):
579
"""Clean up session resources."""
580
session_id = id(session)
581
582
# Clean up temporary files
583
if session_id in session_data:
584
temp_files = session_data[session_id].get("temporary_files", [])
585
for file_path in temp_files:
586
try:
587
os.remove(file_path)
588
except FileNotFoundError:
589
pass
590
591
# Calculate session duration
592
start_time = session_data[session_id]["start_time"]
593
duration = datetime.now() - start_time
594
595
logger.info(f"Session {session_id} ended after {duration}")
596
597
# Remove session data
598
del session_data[session_id]
599
600
# Register lifecycle callbacks
601
on_session_start(initialize_user_session)
602
on_session_end(cleanup_user_session)
603
604
# Global session tracking
605
session_data: dict[int, dict[str, object]] = {}
606
607
def server(input: Inputs, output: Outputs, session: Session):
608
session_id = id(session)
609
610
# Access session-specific data
611
@reactive.calc
612
def user_preferences():
613
return session_data.get(session_id, {}).get("user_preferences", {})
614
615
# Track temporary files
616
@reactive.effect
617
@reactive.event(input.generate_report)
618
def create_temp_report():
619
temp_file = f"/tmp/report_{session_id}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
620
621
# Generate report
622
generate_pdf_report(input.report_data(), temp_file)
623
624
# Track the file for cleanup
625
session_data[session_id]["temporary_files"].append(temp_file)
626
627
# Trigger download
628
session.download("report_download", temp_file)
629
```