or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

custom-objects.mdindex.mdnetwork.mdobjectid.mdserialization.mdtypes.md

custom-objects.mddocs/

0

# Custom Object Serialization

1

2

Framework for creating custom BSON-serializable objects through the BSONCoding abstract base class. This system enables automatic serialization and deserialization of custom Python objects with class registration and metadata preservation.

3

4

## Capabilities

5

6

### BSONCoding Abstract Base Class

7

8

Abstract base class that defines the interface for custom BSON-serializable objects, requiring implementation of encoding and initialization methods.

9

10

```python { .api }

11

from abc import ABCMeta, abstractmethod

12

13

class BSONCoding:

14

__metaclass__ = ABCMeta

15

16

@abstractmethod

17

def bson_encode(self):

18

"""

19

Serialize object state to dictionary for BSON encoding.

20

21

Returns:

22

dict: Dictionary representation of object state

23

"""

24

25

@abstractmethod

26

def bson_init(self, raw_values):

27

"""

28

Initialize object from deserialized BSON data.

29

30

Parameters:

31

- raw_values: dict, deserialized data including class metadata

32

33

Returns:

34

object: Initialized object instance (usually self) or alternative object

35

"""

36

```

37

38

Usage example:

39

40

```python

41

from bson.codec import BSONCoding

42

import bson

43

44

class Person(BSONCoding):

45

def __init__(self, name, age, email=None):

46

self.name = name

47

self.age = age

48

self.email = email

49

50

def bson_encode(self):

51

"""Convert to dict for BSON serialization"""

52

data = {

53

"name": self.name,

54

"age": self.age

55

}

56

if self.email:

57

data["email"] = self.email

58

return data

59

60

def bson_init(self, raw_values):

61

"""Initialize from BSON data"""

62

self.name = raw_values["name"]

63

self.age = raw_values["age"]

64

self.email = raw_values.get("email")

65

return self

66

67

def __repr__(self):

68

return f"Person(name='{self.name}', age={self.age}, email='{self.email}')"

69

70

# Register class for deserialization

71

bson.import_class(Person)

72

73

# Create and serialize

74

person = Person("Alice", 30, "alice@example.com")

75

bson_data = bson.dumps(person)

76

77

# Deserialize back to object

78

restored_person = bson.loads(bson_data)

79

print(type(restored_person)) # <class '__main__.Person'>

80

print(restored_person) # Person(name='Alice', age=30, email='alice@example.com')

81

```

82

83

### Class Registration Functions

84

85

Functions for registering BSONCoding subclasses to enable automatic deserialization by class name.

86

87

```python { .api }

88

def import_class(cls):

89

"""

90

Register a BSONCoding subclass for deserialization.

91

92

Parameters:

93

- cls: BSONCoding subclass to register

94

95

Note: Only registers classes that inherit from BSONCoding

96

"""

97

98

def import_classes(*args):

99

"""

100

Register multiple BSONCoding subclasses.

101

102

Parameters:

103

- args: Variable number of BSONCoding subclasses

104

"""

105

106

def import_classes_from_modules(*args):

107

"""

108

Register all BSONCoding subclasses from modules.

109

110

Parameters:

111

- args: Variable number of module objects to scan for BSONCoding classes

112

"""

113

```

114

115

Usage example:

116

117

```python

118

from bson.codec import BSONCoding, import_class, import_classes, import_classes_from_modules

119

import bson

120

121

class User(BSONCoding):

122

def __init__(self, username, role="user"):

123

self.username = username

124

self.role = role

125

126

def bson_encode(self):

127

return {"username": self.username, "role": self.role}

128

129

def bson_init(self, raw_values):

130

self.username = raw_values["username"]

131

self.role = raw_values.get("role", "user")

132

return self

133

134

class Product(BSONCoding):

135

def __init__(self, name, price):

136

self.name = name

137

self.price = price

138

139

def bson_encode(self):

140

return {"name": self.name, "price": self.price}

141

142

def bson_init(self, raw_values):

143

self.name = raw_values["name"]

144

self.price = raw_values["price"]

145

return self

146

147

# Register individual classes

148

import_class(User)

149

bson.import_class(Product) # Alternative syntax

150

151

# Register multiple classes at once

152

# import_classes(User, Product)

153

154

# Register all BSONCoding classes from a module

155

# import bson.import_classes_from_modules(my_models_module)

156

157

# Test serialization

158

user = User("alice", "admin")

159

product = Product("Laptop", 999.99)

160

161

data = {

162

"user": user,

163

"product": product,

164

"timestamp": "2023-01-01"

165

}

166

167

bson_data = bson.dumps(data)

168

restored = bson.loads(bson_data)

169

170

print(type(restored["user"])) # <class '__main__.User'>

171

print(type(restored["product"])) # <class '__main__.Product'>

172

```

173

174

### Advanced BSONCoding Patterns

175

176

#### Alternative Object Return

177

178

The `bson_init` method can return a different object instance:

179

180

```python

181

from bson.codec import BSONCoding

182

import bson

183

184

class Singleton(BSONCoding):

185

_instance = None

186

187

def __new__(cls, *args, **kwargs):

188

if cls._instance is None:

189

cls._instance = super().__new__(cls)

190

return cls._instance

191

192

def __init__(self, name="default"):

193

if not hasattr(self, 'initialized'):

194

self.name = name

195

self.initialized = True

196

197

def bson_encode(self):

198

return {"name": self.name}

199

200

def bson_init(self, raw_values):

201

# Return the singleton instance instead of self

202

instance = Singleton(raw_values["name"])

203

return instance

204

205

bson.import_class(Singleton)

206

207

# Test singleton behavior through BSON

208

obj1 = Singleton("test")

209

bson_data = bson.dumps(obj1)

210

obj2 = bson.loads(bson_data)

211

212

print(obj1 is obj2) # True (same instance)

213

```

214

215

#### Nested Custom Objects

216

217

Custom objects can contain other custom objects:

218

219

```python

220

from bson.codec import BSONCoding

221

import bson

222

223

class Address(BSONCoding):

224

def __init__(self, street, city, country):

225

self.street = street

226

self.city = city

227

self.country = country

228

229

def bson_encode(self):

230

return {

231

"street": self.street,

232

"city": self.city,

233

"country": self.country

234

}

235

236

def bson_init(self, raw_values):

237

self.street = raw_values["street"]

238

self.city = raw_values["city"]

239

self.country = raw_values["country"]

240

return self

241

242

class Customer(BSONCoding):

243

def __init__(self, name, address, orders=None):

244

self.name = name

245

self.address = address # Address object

246

self.orders = orders or []

247

248

def bson_encode(self):

249

return {

250

"name": self.name,

251

"address": self.address, # Will be recursively encoded

252

"orders": self.orders

253

}

254

255

def bson_init(self, raw_values):

256

self.name = raw_values["name"]

257

self.address = raw_values["address"] # Already deserialized as Address

258

self.orders = raw_values.get("orders", [])

259

return self

260

261

# Register both classes

262

bson.import_classes(Address, Customer)

263

264

# Create nested objects

265

address = Address("123 Main St", "Anytown", "USA")

266

customer = Customer("John Doe", address, ["order1", "order2"])

267

268

# Serialize and deserialize

269

bson_data = bson.dumps(customer)

270

restored = bson.loads(bson_data)

271

272

print(type(restored)) # <class '__main__.Customer'>

273

print(type(restored.address)) # <class '__main__.Address'>

274

print(restored.address.city) # "Anytown"

275

```

276

277

### Object Encoding and Decoding Process

278

279

#### Encoding Process

280

281

When a BSONCoding object is serialized:

282

283

1. `dumps()` detects BSONCoding instance

284

2. Calls `obj.bson_encode()` to get dictionary representation

285

3. Adds special `"$$__CLASS_NAME__$$"` field with class name

286

4. Encodes resulting dictionary as BSON document

287

288

```python

289

# Internal encoding process example

290

class MyClass(BSONCoding):

291

def __init__(self, value):

292

self.value = value

293

294

def bson_encode(self):

295

return {"value": self.value}

296

297

def bson_init(self, raw_values):

298

self.value = raw_values["value"]

299

return self

300

301

obj = MyClass(42)

302

303

# When dumps(obj) is called:

304

# 1. obj.bson_encode() returns {"value": 42}

305

# 2. Class name added: {"value": 42, "$$__CLASS_NAME__$$": "MyClass"}

306

# 3. Dictionary encoded as BSON

307

```

308

309

#### Decoding Process

310

311

When a BSON document with class metadata is deserialized:

312

313

1. `loads()` detects `"$$__CLASS_NAME__$$"` field in dictionary

314

2. Looks up registered class by name

315

3. Creates empty instance with special `_EmptyClass` technique

316

4. Calls `instance.bson_init(raw_values)` to initialize

317

5. Returns initialized object

318

319

```python

320

# Internal decoding process

321

# 1. BSON decoded to: {"value": 42, "$$__CLASS_NAME__$$": "MyClass"}

322

# 2. Class lookup finds MyClass in registry

323

# 3. Empty instance created and class changed to MyClass

324

# 4. instance.bson_init({"value": 42, "$$__CLASS_NAME__$$": "MyClass"}) called

325

# 5. Initialized MyClass instance returned

326

```

327

328

## Error Handling

329

330

### Missing Class Registration

331

332

```python { .api }

333

class MissingClassDefinition(ValueError):

334

"""Raised when trying to deserialize unknown class"""

335

def __init__(self, class_name): ...

336

```

337

338

Occurs when BSON data contains class name not registered with `import_class()`:

339

340

```python

341

from bson.codec import BSONCoding, MissingClassDefinition

342

import bson

343

344

class UnregisteredClass(BSONCoding):

345

def bson_encode(self):

346

return {"data": "test"}

347

348

def bson_init(self, raw_values):

349

return self

350

351

# Serialize without registering

352

obj = UnregisteredClass()

353

bson_data = bson.dumps(obj)

354

355

# Attempting to deserialize fails

356

try:

357

restored = bson.loads(bson_data)

358

except MissingClassDefinition as e:

359

print(f"Class not registered: {e}")

360

361

# Solution: register the class

362

bson.import_class(UnregisteredClass)

363

restored = bson.loads(bson_data) # Now works

364

```

365

366

### BSONCoding Interface Violations

367

368

Classes must properly implement the abstract methods:

369

370

```python

371

from bson.codec import BSONCoding

372

373

# Incorrect - missing abstract method implementations

374

class BadClass(BSONCoding):

375

pass

376

377

try:

378

obj = BadClass() # Raises TypeError

379

except TypeError as e:

380

print(f"Abstract method error: {e}")

381

382

# Correct - implement both abstract methods

383

class GoodClass(BSONCoding):

384

def bson_encode(self):

385

return {}

386

387

def bson_init(self, raw_values):

388

return self

389

```

390

391

## Best Practices

392

393

### Serialization Design

394

395

- Include only essential data in `bson_encode()` return value

396

- Handle optional fields gracefully in `bson_init()`

397

- Consider version compatibility when changing object structure

398

- Use meaningful field names that won't conflict with BSON metadata

399

400

### Class Registration

401

402

- Register classes immediately after definition or in module initialization

403

- Use `import_classes_from_modules()` for automatic registration

404

- Be careful with class name conflicts in different modules

405

406

### Performance Considerations

407

408

- BSONCoding objects add overhead compared to plain dictionaries

409

- Nested custom objects multiply serialization cost

410

- Consider using regular dictionaries for simple data structures