or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-types.mdbasic-types.mdconfiguration.mdcontainer-types.mdcore-traits.mdindex.mdlinking.mdobservers.md

linking.mddocs/

0

# Trait Linking

1

2

Functions for linking traits between objects, enabling synchronization of trait values across different instances. This provides powerful mechanisms for building reactive interfaces and synchronized data structures.

3

4

## Capabilities

5

6

### Bidirectional Linking

7

8

Creates two-way synchronization between traits on different objects.

9

10

```python { .api }

11

class link:

12

"""

13

Bidirectional link between traits on different objects.

14

15

When either linked trait changes, the other automatically

16

updates to match. Changes propagate in both directions.

17

"""

18

19

def __init__(self, source, target):

20

"""

21

Create bidirectional link between traits.

22

23

Parameters:

24

- source: tuple - (object, 'trait_name') pair for first trait

25

- target: tuple - (object, 'trait_name') pair for second trait

26

27

Returns:

28

link - Link object that can be used to unlink later

29

"""

30

31

def unlink(self):

32

"""

33

Remove the bidirectional link.

34

35

After calling this method, changes to either trait will

36

no longer propagate to the other.

37

"""

38

```

39

40

### Directional Linking

41

42

Creates one-way synchronization from source to target trait.

43

44

```python { .api }

45

class directional_link:

46

"""

47

Directional link from source to target trait.

48

49

When the source trait changes, the target automatically updates.

50

Changes to the target do not affect the source.

51

"""

52

53

def __init__(self, source, target, transform=None):

54

"""

55

Create directional link from source to target.

56

57

Parameters:

58

- source: tuple - (object, 'trait_name') pair for source trait

59

- target: tuple - (object, 'trait_name') pair for target trait

60

- transform: callable|None - Optional function to transform values

61

62

The transform function receives the source value and should

63

return the value to set on the target trait.

64

65

Returns:

66

directional_link - Link object that can be used to unlink later

67

"""

68

69

def unlink(self):

70

"""

71

Remove the directional link.

72

73

After calling this method, changes to the source trait will

74

no longer propagate to the target.

75

"""

76

77

# Alias for directional_link

78

dlink = directional_link

79

```

80

81

## Usage Examples

82

83

### Basic Bidirectional Linking

84

85

```python

86

from traitlets import HasTraits, Unicode, Int, link

87

88

class Model(HasTraits):

89

name = Unicode()

90

value = Int()

91

92

class View(HasTraits):

93

display_name = Unicode()

94

display_value = Int()

95

96

# Create instances

97

model = Model(name="Initial", value=42)

98

view = View()

99

100

# Create bidirectional links

101

name_link = link((model, 'name'), (view, 'display_name'))

102

value_link = link((model, 'value'), (view, 'display_value'))

103

104

print(view.display_name) # "Initial" (synchronized from model)

105

print(view.display_value) # 42 (synchronized from model)

106

107

# Changes propagate both ways

108

model.name = "Updated"

109

print(view.display_name) # "Updated"

110

111

view.display_value = 100

112

print(model.value) # 100

113

114

# Clean up links

115

name_link.unlink()

116

value_link.unlink()

117

```

118

119

### Directional Linking with Transform

120

121

```python

122

from traitlets import HasTraits, Unicode, Float, directional_link

123

124

class TemperatureSensor(HasTraits):

125

celsius = Float()

126

127

class Display(HasTraits):

128

fahrenheit = Unicode()

129

kelvin = Unicode()

130

131

def celsius_to_fahrenheit(celsius):

132

return f"{celsius * 9/5 + 32:.1f}°F"

133

134

def celsius_to_kelvin(celsius):

135

return f"{celsius + 273.15:.1f}K"

136

137

# Create instances

138

sensor = TemperatureSensor()

139

display = Display()

140

141

# Create directional links with transforms

142

fahrenheit_link = directional_link(

143

(sensor, 'celsius'),

144

(display, 'fahrenheit'),

145

transform=celsius_to_fahrenheit

146

)

147

148

kelvin_link = directional_link(

149

(sensor, 'celsius'),

150

(display, 'kelvin'),

151

transform=celsius_to_kelvin

152

)

153

154

# Changes in sensor update display

155

sensor.celsius = 25.0

156

print(display.fahrenheit) # "77.0°F"

157

print(display.kelvin) # "298.2K"

158

159

sensor.celsius = 0.0

160

print(display.fahrenheit) # "32.0°F"

161

print(display.kelvin) # "273.2K"

162

163

# Changes in display don't affect sensor (directional only)

164

display.fahrenheit = "100.0°F"

165

print(sensor.celsius) # Still 0.0

166

```

167

168

### Multiple Object Synchronization

169

170

```python

171

from traitlets import HasTraits, Unicode, Bool, link, directional_link

172

173

class ConfigA(HasTraits):

174

theme = Unicode(default_value="light")

175

debug = Bool(default_value=False)

176

177

class ConfigB(HasTraits):

178

ui_theme = Unicode()

179

verbose = Bool()

180

181

class ConfigC(HasTraits):

182

color_scheme = Unicode()

183

debug_mode = Bool()

184

185

# Create instances

186

config_a = ConfigA()

187

config_b = ConfigB()

188

config_c = ConfigC()

189

190

# Create bidirectional links for theme synchronization

191

theme_link_ab = link((config_a, 'theme'), (config_b, 'ui_theme'))

192

theme_link_ac = link((config_a, 'theme'), (config_c, 'color_scheme'))

193

194

# Create directional links for debug mode

195

debug_link_ab = directional_link((config_a, 'debug'), (config_b, 'verbose'))

196

debug_link_ac = directional_link((config_a, 'debug'), (config_c, 'debug_mode'))

197

198

# All themes synchronized

199

config_a.theme = "dark"

200

print(config_b.ui_theme) # "dark"

201

print(config_c.color_scheme) # "dark"

202

203

config_c.color_scheme = "high_contrast"

204

print(config_a.theme) # "high_contrast"

205

print(config_b.ui_theme) # "high_contrast"

206

207

# Debug flows from A to B and C only

208

config_a.debug = True

209

print(config_b.verbose) # True

210

print(config_c.debug_mode) # True

211

212

config_b.verbose = False # Doesn't affect config_a.debug

213

print(config_a.debug) # Still True

214

```

215

216

### Dynamic Linking and Unlinking

217

218

```python

219

from traitlets import HasTraits, Int, observe, link

220

221

class Counter(HasTraits):

222

value = Int()

223

224

class Display(HasTraits):

225

count = Int()

226

227

@observe('count')

228

def _count_changed(self, change):

229

print(f"Display updated: {change['new']}")

230

231

counter1 = Counter()

232

counter2 = Counter()

233

display = Display()

234

235

# Initially link counter1 to display

236

current_link = link((counter1, 'value'), (display, 'count'))

237

238

counter1.value = 10 # Display updated: 10

239

240

# Switch to counter2

241

current_link.unlink()

242

current_link = link((counter2, 'value'), (display, 'count'))

243

244

counter1.value = 20 # No update (unlinked)

245

counter2.value = 30 # Display updated: 30

246

247

# Multiple displays

248

display2 = Display()

249

link2 = link((counter2, 'value'), (display2, 'count'))

250

251

counter2.value = 40 # Both displays update

252

```

253

254

### Complex Transform Functions

255

256

```python

257

from traitlets import HasTraits, List, Unicode, directional_link

258

259

class DataSource(HasTraits):

260

items = List()

261

262

class FormattedDisplay(HasTraits):

263

formatted_text = Unicode()

264

265

def format_list(items):

266

if not items:

267

return "No items"

268

elif len(items) == 1:

269

return f"1 item: {items[0]}"

270

else:

271

return f"{len(items)} items: {', '.join(str(item) for item in items[:3])}" + \

272

("..." if len(items) > 3 else "")

273

274

# Create instances

275

source = DataSource()

276

display = FormattedDisplay()

277

278

# Link with formatting transform

279

formatter_link = directional_link(

280

(source, 'items'),

281

(display, 'formatted_text'),

282

transform=format_list

283

)

284

285

source.items = []

286

print(display.formatted_text) # "No items"

287

288

source.items = ["apple"]

289

print(display.formatted_text) # "1 item: apple"

290

291

source.items = ["apple", "banana", "cherry"]

292

print(display.formatted_text) # "3 items: apple, banana, cherry"

293

294

source.items = ["apple", "banana", "cherry", "date", "elderberry"]

295

print(display.formatted_text) # "5 items: apple, banana, cherry..."

296

```

297

298

### Linking with Validation

299

300

```python

301

from traitlets import HasTraits, Int, TraitError, validate, link

302

303

class Source(HasTraits):

304

value = Int()

305

306

class Target(HasTraits):

307

constrained_value = Int()

308

309

@validate('constrained_value')

310

def _validate_constrained_value(self, proposal):

311

value = proposal['value']

312

if value < 0:

313

raise TraitError("Value must be non-negative")

314

if value > 100:

315

return 100 # Clamp to maximum

316

return value

317

318

source = Source()

319

target = Target()

320

321

# Link with validation on target

322

value_link = link((source, 'value'), (target, 'constrained_value'))

323

324

source.value = 50

325

print(target.constrained_value) # 50

326

327

source.value = 150

328

print(target.constrained_value) # 100 (clamped)

329

330

# This would cause validation error if set directly on target

331

# target.constrained_value = -10 # TraitError

332

# But through linking, negative values from source are handled

333

try:

334

source.value = -10

335

except TraitError as e:

336

print(f"Validation error: {e}")

337

```

338

339

### Conditional Linking

340

341

```python

342

from traitlets import HasTraits, Bool, Unicode, observe

343

344

class ConditionalLinker(HasTraits):

345

enabled = Bool(default_value=True)

346

347

def __init__(self, source, target, **kwargs):

348

super().__init__(**kwargs)

349

self.source = source

350

self.target = target

351

self.current_link = None

352

self._update_link()

353

354

@observe('enabled')

355

def _update_link(self, change=None):

356

# Remove existing link

357

if self.current_link:

358

self.current_link.unlink()

359

self.current_link = None

360

361

# Create new link if enabled

362

if self.enabled:

363

from traitlets import link

364

self.current_link = link(self.source, self.target)

365

366

class Model(HasTraits):

367

data = Unicode()

368

369

class View(HasTraits):

370

display = Unicode()

371

372

model = Model()

373

view = View()

374

375

# Conditional linking

376

linker = ConditionalLinker((model, 'data'), (view, 'display'))

377

378

model.data = "test1"

379

print(view.display) # "test1" (linked)

380

381

# Disable linking

382

linker.enabled = False

383

model.data = "test2"

384

print(view.display) # Still "test1" (not linked)

385

386

# Re-enable linking

387

linker.enabled = True

388

model.data = "test3"

389

print(view.display) # "test3" (linked again)

390

```