0
# Types and Exceptions
1
2
Type definitions, exception classes, and utility types used throughout the Shiny framework. This module provides the foundational types that enable type safety and proper error handling in Shiny applications.
3
4
## Capabilities
5
6
### Exception Classes
7
8
Custom exception types for handling different error conditions in Shiny applications.
9
10
```python { .api }
11
class SafeException(Exception):
12
"""
13
Exception that is safe to display to users in production.
14
15
Unlike regular exceptions, SafeException messages are shown to users
16
even when error sanitization is enabled, making them suitable for
17
user-facing error messages.
18
"""
19
def __init__(self, message: str) -> None:
20
"""Create a safe exception with a user-friendly message."""
21
22
class SilentException(Exception):
23
"""
24
Exception that fails silently without displaying error messages.
25
26
Used internally by functions like req() to cancel computation
27
without showing error messages to users.
28
"""
29
def __init__(self, message: str = "") -> None:
30
"""Create a silent exception."""
31
32
class SilentCancelOutputException(SilentException):
33
"""
34
Silent exception that cancels output without clearing existing content.
35
36
Similar to SilentException but preserves any existing output content
37
instead of clearing it.
38
"""
39
def __init__(self, message: str = "") -> None:
40
"""Create a silent cancel output exception."""
41
```
42
43
#### Usage Examples
44
45
```python
46
from shiny.types import SafeException, SilentException
47
48
def server(input: Inputs, output: Outputs, session: Session):
49
50
@output
51
@render.text
52
def data_summary():
53
try:
54
data = load_data(input.data_source())
55
return f"Loaded {len(data)} records"
56
57
except FileNotFoundError:
58
# Safe to show to users
59
raise SafeException("The selected data file could not be found. Please check your selection.")
60
61
except PermissionError:
62
# Safe user message for permission issues
63
raise SafeException("You don't have permission to access this data file.")
64
65
except Exception as e:
66
# Generic error - don't expose internal details
67
raise SafeException("An error occurred while loading the data. Please try again.")
68
69
@output
70
@render.plot
71
def analysis_plot():
72
# Use req() which raises SilentException internally
73
req(input.x_variable(), input.y_variable())
74
75
data = current_data()
76
if len(data) == 0:
77
# Silently cancel without showing error
78
raise SilentException("No data available")
79
80
return create_plot(data, input.x_variable(), input.y_variable())
81
82
@output
83
@render.table
84
def filtered_data():
85
base_data = get_base_data()
86
87
# Apply filters
88
filters = input.active_filters()
89
if not filters:
90
# Cancel output but don't clear existing table
91
raise SilentCancelOutputException("No filters active")
92
93
return apply_filters(base_data, filters)
94
```
95
96
### File Handling Types
97
98
Type definitions for handling file uploads and file information.
99
100
```python { .api }
101
class FileInfo(TypedDict):
102
"""
103
Information about an uploaded file.
104
105
Contains metadata about files uploaded through file input controls,
106
providing access to file properties and the server-side file path.
107
"""
108
name: str
109
"""Original filename as provided by the user."""
110
111
size: int
112
"""File size in bytes."""
113
114
type: str
115
"""MIME type of the uploaded file."""
116
117
datapath: str
118
"""Server-side path where the uploaded file is stored."""
119
```
120
121
#### Usage Examples
122
123
```python
124
from shiny.types import FileInfo
125
import pandas as pd
126
import os
127
128
def server(input: Inputs, output: Outputs, session: Session):
129
130
@output
131
@render.text
132
def file_info():
133
file: FileInfo | None = input.uploaded_file()
134
135
if file is None:
136
return "No file uploaded"
137
138
# Access file metadata
139
size_mb = file["size"] / (1024 * 1024)
140
141
return f"""
142
File Information:
143
- Name: {file['name']}
144
- Size: {size_mb:.2f} MB
145
- Type: {file['type']}
146
- Server Path: {file['datapath']}
147
"""
148
149
@output
150
@render.data_frame
151
def uploaded_data():
152
file: FileInfo | None = input.data_file()
153
154
if file is None:
155
return pd.DataFrame() # Empty DataFrame
156
157
# Validate file type
158
if not file["type"].startswith("text/csv"):
159
raise SafeException("Please upload a CSV file")
160
161
# Validate file size (10MB limit)
162
if file["size"] > 10 * 1024 * 1024:
163
raise SafeException("File size must be less than 10MB")
164
165
try:
166
# Read file using server path
167
data = pd.read_csv(file["datapath"])
168
return data
169
170
except pd.errors.EmptyDataError:
171
raise SafeException("The uploaded file is empty")
172
173
except pd.errors.ParserError:
174
raise SafeException("Unable to parse the CSV file. Please check the format.")
175
176
@reactive.effect
177
@reactive.event(input.process_file)
178
def process_uploaded_file():
179
file: FileInfo | None = input.processing_file()
180
181
if file is None:
182
return
183
184
# Create processed filename
185
base_name = os.path.splitext(file["name"])[0]
186
processed_name = f"{base_name}_processed.csv"
187
188
# Process the file
189
try:
190
original_data = pd.read_csv(file["datapath"])
191
processed_data = perform_data_processing(original_data)
192
193
# Save processed file
194
output_path = f"/tmp/{processed_name}"
195
processed_data.to_csv(output_path, index=False)
196
197
# Trigger download
198
session.download("processed_download", output_path)
199
200
except Exception as e:
201
raise SafeException(f"Error processing file: {str(e)}")
202
```
203
204
### Image Rendering Types
205
206
Type definitions for image rendering and display.
207
208
```python { .api }
209
class ImgData(TypedDict):
210
"""
211
Image data structure for render.image() output.
212
213
Provides complete control over image rendering including source,
214
dimensions, accessibility, and styling options.
215
"""
216
src: str
217
"""Image source URL or data URI."""
218
219
width: NotRequired[str | float]
220
"""Image width (CSS units or pixels)."""
221
222
height: NotRequired[str | float]
223
"""Image height (CSS units or pixels)."""
224
225
alt: NotRequired[str]
226
"""Alt text for accessibility."""
227
228
style: NotRequired[str]
229
"""CSS styles to apply to the image."""
230
231
coordmap: NotRequired[Any]
232
"""Coordinate mapping for interactive plots."""
233
```
234
235
#### Usage Examples
236
237
```python
238
from shiny.types import ImgData
239
import base64
240
import io
241
from PIL import Image, ImageDraw
242
243
def server(input: Inputs, output: Outputs, session: Session):
244
245
@output
246
@render.image
247
def dynamic_chart() -> ImgData:
248
# Generate image based on inputs
249
width, height = 400, 300
250
img = Image.new('RGB', (width, height), color='white')
251
draw = ImageDraw.Draw(img)
252
253
# Draw content based on user inputs
254
title = input.chart_title() or "Default Chart"
255
color = input.chart_color() or "blue"
256
257
# Draw some sample content
258
draw.rectangle([50, 50, width-50, height-50], outline=color, width=3)
259
draw.text((width//2 - 50, 20), title, fill='black')
260
261
# Convert to base64 data URI
262
buffer = io.BytesIO()
263
img.save(buffer, format='PNG')
264
buffer.seek(0)
265
img_data = base64.b64encode(buffer.getvalue()).decode()
266
267
return {
268
"src": f"data:image/png;base64,{img_data}",
269
"width": "100%",
270
"height": "300px",
271
"alt": f"Dynamic chart: {title}",
272
"style": "border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
273
}
274
275
@output
276
@render.image
277
def responsive_image() -> ImgData:
278
# Create responsive image based on device characteristics
279
pixel_ratio = session.client_data.pixelratio
280
281
# Generate high-DPI image if needed
282
base_width = 300
283
base_height = 200
284
285
actual_width = int(base_width * pixel_ratio)
286
actual_height = int(base_height * pixel_ratio)
287
288
img = create_high_resolution_image(actual_width, actual_height)
289
290
# Save to temporary file
291
temp_path = f"/tmp/responsive_img_{id(session)}.png"
292
img.save(temp_path, format='PNG', dpi=(96 * pixel_ratio, 96 * pixel_ratio))
293
294
return {
295
"src": temp_path,
296
"width": f"{base_width}px",
297
"height": f"{base_height}px",
298
"alt": "Responsive high-DPI image",
299
"style": f"max-width: 100%; height: auto;"
300
}
301
302
@output
303
@render.image
304
def plot_with_interaction() -> ImgData:
305
# Generate plot that supports click interactions
306
plot_data = get_plot_data()
307
308
fig = create_interactive_plot(plot_data)
309
310
# Save plot with coordinate mapping
311
plot_path = "/tmp/interactive_plot.png"
312
fig.savefig(plot_path, dpi=150, bbox_inches='tight')
313
314
# Create coordinate mapping for click events
315
coord_map = generate_coordinate_mapping(fig)
316
317
return {
318
"src": plot_path,
319
"width": "800px",
320
"height": "600px",
321
"alt": "Interactive plot - click for details",
322
"coordmap": coord_map
323
}
324
```
325
326
### Utility Types and Constants
327
328
General utility types and constants used throughout the framework.
329
330
```python { .api }
331
class MISSING_TYPE:
332
"""
333
Type for the MISSING sentinel value.
334
335
Used to distinguish between None (which is a valid value) and
336
a parameter that was not provided at all.
337
"""
338
def __repr__(self) -> str:
339
return "MISSING"
340
341
MISSING: MISSING_TYPE
342
"""
343
Sentinel value indicating a missing/unspecified parameter.
344
345
Used throughout Shiny to distinguish between None (a valid value)
346
and parameters that were not provided by the user.
347
"""
348
349
Jsonifiable = str | int | float | bool | None | dict[str, Any] | list[Any]
350
"""
351
Type alias for values that can be JSON-serialized.
352
353
Represents the types that can be safely converted to JSON for
354
client-server communication in Shiny applications.
355
"""
356
```
357
358
#### Usage Examples
359
360
```python
361
from shiny.types import MISSING, MISSING_TYPE, Jsonifiable
362
363
def optional_parameter_function(
364
required_param: str,
365
optional_param: str | MISSING_TYPE = MISSING
366
) -> str:
367
"""Function demonstrating MISSING sentinel usage."""
368
369
if optional_param is MISSING:
370
# Parameter was not provided
371
return f"Required: {required_param}, Optional: not provided"
372
else:
373
# Parameter was provided (could be None)
374
return f"Required: {required_param}, Optional: {optional_param}"
375
376
# Usage examples
377
result1 = optional_parameter_function("hello")
378
# "Required: hello, Optional: not provided"
379
380
result2 = optional_parameter_function("hello", "world")
381
# "Required: hello, Optional: world"
382
383
result3 = optional_parameter_function("hello", None)
384
# "Required: hello, Optional: None" (None is a valid value)
385
386
# JSON serialization helper
387
def send_data_to_client(data: Jsonifiable) -> None:
388
"""Send JSON-serializable data to client."""
389
import json
390
391
try:
392
json_string = json.dumps(data)
393
session.send_custom_message("data_update", {"data": data})
394
except TypeError as e:
395
raise SafeException(f"Data is not JSON-serializable: {e}")
396
397
def server(input: Inputs, output: Outputs, session: Session):
398
399
@reactive.effect
400
@reactive.event(input.send_data)
401
def send_analysis_results():
402
results = get_analysis_results()
403
404
# Ensure data is JSON-serializable
405
json_data: Jsonifiable = {
406
"summary": results.summary_dict(),
407
"metrics": results.metrics_list(),
408
"timestamp": datetime.now().isoformat(),
409
"success": True
410
}
411
412
send_data_to_client(json_data)
413
414
# Function using MISSING sentinel
415
def create_plot_with_optional_title(
416
data: pd.DataFrame,
417
title: str | MISSING_TYPE = MISSING
418
):
419
fig, ax = plt.subplots()
420
ax.plot(data['x'], data['y'])
421
422
if title is not MISSING:
423
ax.set_title(title)
424
else:
425
# Auto-generate title
426
ax.set_title(f"Plot of {data.columns[1]} vs {data.columns[0]}")
427
428
return fig
429
430
@output
431
@render.plot
432
def main_plot():
433
data = current_data()
434
435
# Title input might be empty string, None, or missing
436
user_title = input.plot_title()
437
438
if user_title: # Non-empty string
439
return create_plot_with_optional_title(data, user_title)
440
else: # Empty string or None - use auto title
441
return create_plot_with_optional_title(data) # MISSING used
442
```
443
444
### HTML and UI Types
445
446
Type aliases for HTML and UI component construction.
447
448
```python { .api }
449
# From htmltools (re-exported by shiny.types)
450
Tag: TypeAlias
451
"""HTML tag object."""
452
453
TagAttrs: TypeAlias
454
"""HTML tag attributes dictionary."""
455
456
TagAttrValue: TypeAlias
457
"""Valid values for HTML tag attributes."""
458
459
TagChild: TypeAlias
460
"""Valid child content for HTML tags."""
461
462
TagList: TypeAlias
463
"""List of HTML tags or tag children."""
464
```
465
466
#### Usage Examples
467
468
```python
469
from shiny.types import Tag, TagChild, TagAttrs
470
from shiny import ui
471
472
def create_custom_card(
473
title: str,
474
content: TagChild,
475
**attrs: TagAttrs
476
) -> Tag:
477
"""Create a custom card component."""
478
479
return ui.div(
480
ui.div(
481
ui.h4(title, class_="card-title"),
482
class_="card-header"
483
),
484
ui.div(
485
content,
486
class_="card-body"
487
),
488
class_="card",
489
**attrs
490
)
491
492
def create_info_section(
493
items: list[tuple[str, TagChild]]
494
) -> Tag:
495
"""Create an information section from key-value pairs."""
496
497
info_items = []
498
for key, value in items:
499
info_items.append(
500
ui.div(
501
ui.strong(f"{key}: "),
502
value,
503
class_="info-item"
504
)
505
)
506
507
return ui.div(
508
*info_items,
509
class_="info-section"
510
)
511
512
# Usage in server function
513
def server(input: Inputs, output: Outputs, session: Session):
514
515
@output
516
@render.ui
517
def dynamic_card():
518
return create_custom_card(
519
title="Analysis Results",
520
content=ui.div(
521
ui.p(f"Dataset has {len(current_data())} rows"),
522
ui.p(f"Selected variables: {input.variables()}")
523
),
524
id="results-card",
525
style="margin: 20px 0;"
526
)
527
528
@output
529
@render.ui
530
def system_info():
531
return create_info_section([
532
("Session ID", str(id(session))),
533
("Client IP", session.client_data.url_hostname),
534
("User Agent", "Modern Browser"),
535
("Connected At", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
536
])
537
```
538
539
### Type Checking and Validation
540
541
Utilities for runtime type checking and validation.
542
543
```python { .api }
544
def is_jsonifiable(obj: Any) -> bool:
545
"""
546
Check if an object can be JSON-serialized.
547
548
Args:
549
obj: Object to check.
550
551
Returns:
552
True if object is JSON-serializable.
553
"""
554
555
def validate_file_info(obj: Any) -> FileInfo:
556
"""
557
Validate and return a FileInfo object.
558
559
Args:
560
obj: Object to validate as FileInfo.
561
562
Returns:
563
Validated FileInfo object.
564
565
Raises:
566
TypeError: If object is not a valid FileInfo.
567
"""
568
569
def validate_img_data(obj: Any) -> ImgData:
570
"""
571
Validate and return an ImgData object.
572
573
Args:
574
obj: Object to validate as ImgData.
575
576
Returns:
577
Validated ImgData object.
578
579
Raises:
580
TypeError: If object is not a valid ImgData.
581
"""
582
```
583
584
#### Usage Examples
585
586
```python
587
from shiny.types import is_jsonifiable, validate_file_info, validate_img_data
588
589
def server(input: Inputs, output: Outputs, session: Session):
590
591
@reactive.calc
592
def safe_json_data():
593
raw_data = get_raw_analysis_results()
594
595
# Ensure all data is JSON-serializable before sending to client
596
cleaned_data = {}
597
for key, value in raw_data.items():
598
if is_jsonifiable(value):
599
cleaned_data[key] = value
600
else:
601
# Convert non-serializable data to string representation
602
cleaned_data[key] = str(value)
603
604
return cleaned_data
605
606
@output
607
@render.text
608
def file_validation_result():
609
uploaded = input.data_file()
610
611
if uploaded is None:
612
return "No file uploaded"
613
614
try:
615
# Validate file info structure
616
file_info = validate_file_info(uploaded)
617
return f"Valid file: {file_info['name']} ({file_info['size']} bytes)"
618
619
except TypeError as e:
620
return f"Invalid file info: {e}"
621
622
@output
623
@render.image
624
def validated_image():
625
try:
626
img_data = generate_image_data()
627
628
# Validate image data structure
629
validated = validate_img_data(img_data)
630
return validated
631
632
except TypeError as e:
633
# Return error image
634
return {
635
"src": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjEwMCI+PHRleHQgeD0iMTAiIHk9IjUwIj5FcnJvcjwvdGV4dD48L3N2Zz4=",
636
"alt": f"Image generation error: {e}"
637
}
638
```