or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app.mdexpress.mdindex.mdreactive.mdrender.mdsession.mdtypes.mdui.md

session.mddocs/

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

```