or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-widget.mdexperimental.mdfile-management.mdindex.mdipython-integration.md

file-management.mddocs/

0

# File Management

1

2

Dynamic file loading system with live reloading capabilities for development workflows and virtual file management for inline content. This system enables seamless development experiences with hot module replacement and flexible content management.

3

4

## Capabilities

5

6

### FileContents Class

7

8

Watches file changes and emits signals when files are modified, enabling live reloading during development.

9

10

```python { .api }

11

class FileContents:

12

"""

13

Object that watches for file changes and emits a signal when it changes.

14

15

Provides live reloading capabilities by monitoring filesystem changes

16

and automatically updating widget content during development.

17

18

Attributes:

19

changed (Signal): Emitted when file contents change

20

deleted (Signal): Emitted when file is deleted

21

"""

22

23

def __init__(self, path: str | Path, start_thread: bool = True):

24

"""

25

Initialize file watcher for the specified path.

26

27

Parameters:

28

path (str | Path): The file to read and watch for content changes

29

start_thread (bool): Whether to start watching for changes in a separate thread

30

31

Raises:

32

ValueError: If the file does not exist

33

"""

34

35

def watch_in_thread(self):

36

"""

37

Watch for file changes (emitting signals) from a separate thread.

38

39

Starts a background thread that monitors the file for changes

40

and emits the 'changed' signal when modifications are detected.

41

"""

42

43

def stop_thread(self):

44

"""

45

Stops an actively running file watching thread if it exists.

46

47

Cleanly shuts down the background file monitoring thread

48

and releases associated resources.

49

"""

50

51

def watch(self):

52

"""

53

Watch for file changes and emit changed/deleted signal events.

54

55

Blocks indefinitely, yielding change events until the file is deleted.

56

57

Returns:

58

Iterator[tuple[int, str]]: Iterator yielding change events

59

60

Raises:

61

ImportError: If watchfiles package is not installed

62

"""

63

64

def __str__(self) -> str:

65

"""

66

Return current file contents as string.

67

68

Reads and caches file contents, returning the current text.

69

Cache is cleared when file changes are detected.

70

71

Returns:

72

str: Current file contents

73

"""

74

```

75

76

### VirtualFileContents Class

77

78

Stores text file contents in memory with change signals, useful for dynamic content and cell magic integration.

79

80

```python { .api }

81

class VirtualFileContents:

82

"""

83

Stores text file contents in memory and emits a signal when it changes.

84

85

Provides in-memory file simulation with change notifications,

86

useful for dynamic content and integration with IPython cell magics.

87

88

Attributes:

89

changed (Signal): Emitted when contents change

90

contents (str): Current text contents (property)

91

"""

92

93

def __init__(self, contents: str = ""):

94

"""

95

Initialize virtual file with optional initial contents.

96

97

Parameters:

98

contents (str): The initial contents of the file (default: "")

99

"""

100

101

@property

102

def contents(self) -> str:

103

"""

104

Get current file contents.

105

106

Returns:

107

str: Current text contents

108

"""

109

110

@contents.setter

111

def contents(self, value: str):

112

"""

113

Set file contents and emit changed signal.

114

115

Parameters:

116

value (str): New file contents

117

"""

118

119

def __str__(self) -> str:

120

"""

121

Return current contents as string.

122

123

Returns:

124

str: Current file contents

125

"""

126

```

127

128

### File Utilities

129

130

Helper functions for working with file paths and content management.

131

132

```python { .api }

133

def try_file_contents(x) -> FileContents | VirtualFileContents | None:

134

"""

135

Try to coerce an object into a FileContents object.

136

137

Attempts to convert strings, paths, or virtual file references

138

into appropriate FileContents or VirtualFileContents objects.

139

140

Parameters:

141

x: Object to try to coerce (str, Path, etc.)

142

143

Returns:

144

FileContents | VirtualFileContents | None: File contents object or None if conversion fails

145

146

Raises:

147

FileNotFoundError: If the file path exists but file is not found

148

"""

149

150

def try_file_path(x) -> Path | None:

151

"""

152

Try to coerce an object into a pathlib.Path object.

153

154

Handles various input types and validates file path patterns:

155

- Returns None for URLs or multi-line strings

156

- Returns Path for single-line strings with file extensions

157

- Returns existing Path objects unchanged

158

159

Parameters:

160

x: Object to try to coerce into a path

161

162

Returns:

163

Path | None: Path object if x is a valid file path, otherwise None

164

"""

165

```

166

167

### Global Registry

168

169

```python { .api }

170

_VIRTUAL_FILES: weakref.WeakValueDictionary[str, VirtualFileContents]

171

"""

172

Global registry of virtual files.

173

174

Weak reference dictionary that stores virtual file contents

175

by name, automatically cleaning up unused references.

176

"""

177

```

178

179

## Usage Examples

180

181

### File-Based Widget Development

182

183

```python

184

import anywidget

185

186

class DevelopmentWidget(anywidget.AnyWidget):

187

# Files are automatically watched for changes

188

_esm = "./widget.js" # Reloads when widget.js changes

189

_css = "./widget.css" # Reloads when widget.css changes

190

191

# Enable hot module replacement for development

192

import os

193

os.environ["ANYWIDGET_HMR"] = "1"

194

195

widget = DevelopmentWidget()

196

# Edit widget.js or widget.css - changes appear immediately

197

```

198

199

### Manual File Watching

200

201

```python

202

from anywidget._file_contents import FileContents

203

import pathlib

204

205

# Create file watcher

206

js_file = FileContents("./my-widget.js")

207

208

# Connect to change events

209

@js_file.changed.connect

210

def on_js_change(new_contents):

211

print(f"JavaScript updated: {len(new_contents)} characters")

212

213

@js_file.deleted.connect

214

def on_js_deleted():

215

print("JavaScript file was deleted!")

216

217

# Access current contents

218

print(str(js_file)) # Reads current file contents

219

220

# Stop watching when done

221

js_file.stop_thread()

222

```

223

224

### Virtual File Management

225

226

```python

227

from anywidget._file_contents import VirtualFileContents, _VIRTUAL_FILES

228

229

# Create virtual file

230

virtual_js = VirtualFileContents("""

231

function render({ model, el }) {

232

el.innerHTML = "<h1>Dynamic Content</h1>";

233

}

234

export default { render };

235

""")

236

237

# Register in global registry

238

_VIRTUAL_FILES["my-widget"] = virtual_js

239

240

# Connect to change events

241

@virtual_js.changed.connect

242

def on_virtual_change(new_contents):

243

print("Virtual file updated!")

244

245

# Update contents programmatically

246

virtual_js.contents = """

247

function render({ model, el }) {

248

el.innerHTML = "<h1>Updated Dynamic Content</h1>";

249

}

250

export default { render };

251

"""

252

253

# Use in widget

254

import anywidget

255

256

class VirtualWidget(anywidget.AnyWidget):

257

_esm = "my-widget" # References virtual file by name

258

259

widget = VirtualWidget()

260

```

261

262

### Development Workflow with Live Reloading

263

264

```python

265

import anywidget

266

import traitlets as t

267

from pathlib import Path

268

269

# Create development files

270

js_path = Path("./counter.js")

271

css_path = Path("./counter.css")

272

273

js_path.write_text("""

274

function render({ model, el }) {

275

let count = () => model.get("value");

276

let btn = document.createElement("button");

277

btn.innerHTML = `Count: ${count()}`;

278

btn.className = "counter-btn";

279

280

btn.addEventListener("click", () => {

281

model.set("value", count() + 1);

282

});

283

284

model.on("change:value", () => {

285

btn.innerHTML = `Count: ${count()}`;

286

});

287

288

el.appendChild(btn);

289

}

290

export default { render };

291

""")

292

293

css_path.write_text("""

294

.counter-btn {

295

background: #007cba;

296

color: white;

297

border: none;

298

padding: 10px 20px;

299

border-radius: 5px;

300

cursor: pointer;

301

font-size: 16px;

302

}

303

.counter-btn:hover {

304

background: #005a8b;

305

}

306

""")

307

308

class CounterWidget(anywidget.AnyWidget):

309

_esm = js_path # FileContents automatically created

310

_css = css_path # FileContents automatically created

311

312

value = t.Int(0).tag(sync=True)

313

314

# Enable hot reloading

315

import os

316

os.environ["ANYWIDGET_HMR"] = "1"

317

318

widget = CounterWidget()

319

# Now edit counter.js or counter.css files - changes appear immediately in Jupyter

320

```

321

322

### Custom File Content Processing

323

324

```python

325

from anywidget._file_contents import FileContents

326

import json

327

328

class ConfigFileWatcher:

329

def __init__(self, config_path):

330

self.config_file = FileContents(config_path)

331

self.config = {}

332

self._load_config()

333

334

# Watch for changes

335

self.config_file.changed.connect(self._on_config_change)

336

337

def _load_config(self):

338

"""Load and parse configuration"""

339

try:

340

self.config = json.loads(str(self.config_file))

341

except json.JSONDecodeError:

342

self.config = {}

343

print("Warning: Invalid JSON in config file")

344

345

def _on_config_change(self, new_contents):

346

"""Handle configuration file changes"""

347

print("Configuration file updated, reloading...")

348

self._load_config()

349

self._apply_config()

350

351

def _apply_config(self):

352

"""Apply configuration changes"""

353

print(f"Applied config: {self.config}")

354

355

# Usage

356

config_watcher = ConfigFileWatcher("./app-config.json")

357

# Edit app-config.json - changes are automatically detected and processed

358

```

359

360

### Integration with Widget Updates

361

362

```python

363

import anywidget

364

import traitlets as t

365

from anywidget._file_contents import FileContents

366

367

class ConfigurableWidget(anywidget.AnyWidget):

368

_esm = """

369

function render({ model, el }) {

370

let updateDisplay = () => {

371

let config = model.get("config");

372

el.innerHTML = `

373

<div style="

374

background: ${config.background || '#f0f0f0'};

375

color: ${config.color || '#333'};

376

padding: ${config.padding || '10px'};

377

border-radius: ${config.borderRadius || '5px'};

378

">

379

<h3>${config.title || 'Default Title'}</h3>

380

<p>${config.message || 'Default message'}</p>

381

</div>

382

`;

383

};

384

385

model.on("change:config", updateDisplay);

386

updateDisplay();

387

}

388

export default { render };

389

"""

390

391

config = t.Dict({}).tag(sync=True)

392

393

def __init__(self, config_file=None, **kwargs):

394

super().__init__(**kwargs)

395

396

if config_file:

397

self.config_watcher = FileContents(config_file)

398

self.config_watcher.changed.connect(self._update_from_file)

399

self._update_from_file(str(self.config_watcher))

400

401

def _update_from_file(self, contents):

402

"""Update widget config from file contents"""

403

try:

404

import json

405

new_config = json.loads(contents)

406

self.config = new_config

407

except json.JSONDecodeError:

408

print("Warning: Invalid JSON in config file")

409

410

# Create widget with external config file

411

widget = ConfigurableWidget(config_file="./widget-config.json")

412

# Edit widget-config.json to change widget appearance in real-time

413

```