or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdconfiguration.mdcontainer.mdcore-indexing.mdcustom-storage.mdindex.mdutilities.md

custom-storage.mddocs/

0

# Custom Storage Framework

1

2

Interface and base classes for implementing custom storage backends, enabling integration with databases, cloud storage, network protocols, or specialized data structures. The framework provides both low-level buffer access and high-level Python string marshaling.

3

4

## Capabilities

5

6

### Storage Interface

7

8

Base interface defining the contract for custom storage implementations.

9

10

```python { .api }

11

class ICustomStorage:

12

"""Interface for custom storage implementations."""

13

14

# Error constants

15

NoError = 0

16

InvalidPageError = 1

17

IllegalStateError = 2

18

19

# Page constants

20

EmptyPage = -0x1

21

NewPage = -0x1

22

23

def registerCallbacks(self, properties):

24

"""

25

Register storage callbacks with index properties.

26

27

Parameters:

28

- properties (Property): Property object to configure

29

30

Abstract method that must be implemented by subclasses.

31

"""

32

33

def clear(self):

34

"""

35

Clear all stored data.

36

37

Abstract method that must be implemented by subclasses.

38

"""

39

40

@property

41

def hasData(self) -> bool:

42

"""

43

Indicate whether storage contains data.

44

45

Returns:

46

bool: True if storage has existing data

47

"""

48

49

def allocateBuffer(self, length: int):

50

"""

51

Allocate buffer for data operations.

52

53

Parameters:

54

- length (int): Buffer size in bytes

55

56

Returns:

57

Buffer object for data operations

58

"""

59

```

60

61

### Low-Level Storage Base

62

63

Base class for implementing custom storage with raw C buffer access, providing maximum performance and control.

64

65

```python { .api }

66

class CustomStorageBase(ICustomStorage):

67

"""Base class for raw C buffer access custom storage."""

68

69

def create(self, context, returnError):

70

"""

71

Create storage backend.

72

73

Parameters:

74

- context: Storage context object

75

- returnError: Error reporting callback

76

77

Abstract method for storage initialization.

78

"""

79

80

def destroy(self, context, returnError):

81

"""

82

Destroy storage backend and cleanup resources.

83

84

Parameters:

85

- context: Storage context object

86

- returnError: Error reporting callback

87

88

Abstract method for storage cleanup.

89

"""

90

91

def loadByteArray(self, context, page, resultLen, resultData, returnError):

92

"""

93

Load data from storage backend.

94

95

Parameters:

96

- context: Storage context object

97

- page (int): Page identifier to load

98

- resultLen: Output parameter for data length

99

- resultData: Output parameter for data buffer

100

- returnError: Error reporting callback

101

102

Abstract method for data loading.

103

"""

104

105

def storeByteArray(self, context, page, len, data, returnError):

106

"""

107

Store data to storage backend.

108

109

Parameters:

110

- context: Storage context object

111

- page (int): Page identifier for storage

112

- len (int): Data length in bytes

113

- data: Data buffer to store

114

- returnError: Error reporting callback

115

116

Abstract method for data storage.

117

"""

118

119

def deleteByteArray(self, context, page, returnError):

120

"""

121

Delete data from storage backend.

122

123

Parameters:

124

- context: Storage context object

125

- page (int): Page identifier to delete

126

- returnError: Error reporting callback

127

128

Abstract method for data deletion.

129

"""

130

131

def flush(self, context, returnError):

132

"""

133

Flush pending operations to storage backend.

134

135

Parameters:

136

- context: Storage context object

137

- returnError: Error reporting callback

138

139

Abstract method for storage flushing.

140

"""

141

```

142

143

### High-Level Storage Base

144

145

Base class for implementing custom storage with Python string marshaling, providing easier implementation with automatic data conversion.

146

147

```python { .api }

148

class CustomStorage(CustomStorageBase):

149

"""Default custom storage with Python string marshaling."""

150

151

def create(self, returnError):

152

"""

153

Create storage backend.

154

155

Parameters:

156

- returnError: Error reporting callback

157

158

Abstract method for storage initialization.

159

Simplified interface without context parameter.

160

"""

161

162

def destroy(self, returnError):

163

"""

164

Destroy storage backend and cleanup resources.

165

166

Parameters:

167

- returnError: Error reporting callback

168

169

Abstract method for storage cleanup.

170

Simplified interface without context parameter.

171

"""

172

173

def loadByteArray(self, page, returnError):

174

"""

175

Load data from storage backend as Python string.

176

177

Parameters:

178

- page (int): Page identifier to load

179

- returnError: Error reporting callback

180

181

Returns:

182

str: Loaded data as Python string

183

184

Abstract method for data loading with automatic marshaling.

185

"""

186

187

def storeByteArray(self, page, data, returnError):

188

"""

189

Store Python string data to storage backend.

190

191

Parameters:

192

- page (int): Page identifier for storage

193

- data (str): Python string data to store

194

- returnError: Error reporting callback

195

196

Abstract method for data storage with automatic marshaling.

197

"""

198

199

def deleteByteArray(self, page, returnError):

200

"""

201

Delete data from storage backend.

202

203

Parameters:

204

- page (int): Page identifier to delete

205

- returnError: Error reporting callback

206

207

Abstract method for data deletion.

208

"""

209

210

def flush(self, returnError):

211

"""

212

Flush pending operations to storage backend.

213

214

Parameters:

215

- returnError: Error reporting callback

216

217

Abstract method for storage flushing.

218

"""

219

```

220

221

## Usage Examples

222

223

### Dictionary-Based Custom Storage

224

225

```python

226

from rtree.index import Index, Property, CustomStorage

227

228

class DictStorage(CustomStorage):

229

"""Simple dictionary-based storage for demonstration."""

230

231

def __init__(self):

232

super().__init__()

233

self.data = {}

234

self.has_data = False

235

236

def create(self, returnError):

237

"""Initialize storage."""

238

self.data.clear()

239

self.has_data = True

240

returnError.value = self.NoError

241

242

def destroy(self, returnError):

243

"""Cleanup storage."""

244

self.data.clear()

245

self.has_data = False

246

returnError.value = self.NoError

247

248

def loadByteArray(self, page, returnError):

249

"""Load data for page."""

250

if page in self.data:

251

returnError.value = self.NoError

252

return self.data[page]

253

else:

254

returnError.value = self.InvalidPageError

255

return None

256

257

def storeByteArray(self, page, data, returnError):

258

"""Store data for page."""

259

self.data[page] = data

260

returnError.value = self.NoError

261

262

def deleteByteArray(self, page, returnError):

263

"""Delete data for page."""

264

if page in self.data:

265

del self.data[page]

266

returnError.value = self.NoError

267

else:

268

returnError.value = self.InvalidPageError

269

270

def flush(self, returnError):

271

"""Flush operations (no-op for dict)."""

272

returnError.value = self.NoError

273

274

@property

275

def hasData(self):

276

return self.has_data

277

278

# Use custom storage

279

storage = DictStorage()

280

prop = Property(storage=RT_Custom)

281

storage.registerCallbacks(prop)

282

283

idx = Index(properties=prop, storage=storage)

284

idx.insert(0, (0, 0, 10, 10))

285

```

286

287

### Database-Backed Storage

288

289

```python

290

import sqlite3

291

from rtree.index import Index, Property, CustomStorage

292

293

class SQLiteStorage(CustomStorage):

294

"""SQLite database storage backend."""

295

296

def __init__(self, db_path=":memory:"):

297

super().__init__()

298

self.db_path = db_path

299

self.conn = None

300

301

def create(self, returnError):

302

"""Initialize database storage."""

303

try:

304

self.conn = sqlite3.connect(self.db_path)

305

self.conn.execute("""

306

CREATE TABLE IF NOT EXISTS rtree_pages (

307

page_id INTEGER PRIMARY KEY,

308

data BLOB

309

)

310

""")

311

self.conn.commit()

312

returnError.value = self.NoError

313

except Exception:

314

returnError.value = self.IllegalStateError

315

316

def destroy(self, returnError):

317

"""Cleanup database connection."""

318

try:

319

if self.conn:

320

self.conn.close()

321

self.conn = None

322

returnError.value = self.NoError

323

except Exception:

324

returnError.value = self.IllegalStateError

325

326

def loadByteArray(self, page, returnError):

327

"""Load page data from database."""

328

try:

329

cursor = self.conn.execute(

330

"SELECT data FROM rtree_pages WHERE page_id = ?", (page,)

331

)

332

row = cursor.fetchone()

333

if row:

334

returnError.value = self.NoError

335

return row[0].decode('utf-8')

336

else:

337

returnError.value = self.InvalidPageError

338

return None

339

except Exception:

340

returnError.value = self.IllegalStateError

341

return None

342

343

def storeByteArray(self, page, data, returnError):

344

"""Store page data to database."""

345

try:

346

self.conn.execute(

347

"INSERT OR REPLACE INTO rtree_pages (page_id, data) VALUES (?, ?)",

348

(page, data.encode('utf-8'))

349

)

350

returnError.value = self.NoError

351

except Exception:

352

returnError.value = self.IllegalStateError

353

354

def deleteByteArray(self, page, returnError):

355

"""Delete page data from database."""

356

try:

357

cursor = self.conn.execute(

358

"DELETE FROM rtree_pages WHERE page_id = ?", (page,)

359

)

360

if cursor.rowcount > 0:

361

returnError.value = self.NoError

362

else:

363

returnError.value = self.InvalidPageError

364

except Exception:

365

returnError.value = self.IllegalStateError

366

367

def flush(self, returnError):

368

"""Commit pending transactions."""

369

try:

370

self.conn.commit()

371

returnError.value = self.NoError

372

except Exception:

373

returnError.value = self.IllegalStateError

374

375

@property

376

def hasData(self):

377

if not self.conn:

378

return False

379

cursor = self.conn.execute("SELECT COUNT(*) FROM rtree_pages")

380

return cursor.fetchone()[0] > 0

381

382

# Use SQLite storage

383

storage = SQLiteStorage("spatial_index.db")

384

prop = Property(storage=RT_Custom)

385

storage.registerCallbacks(prop)

386

387

idx = Index(properties=prop, storage=storage)

388

```

389

390

### Network Storage Backend

391

392

```python

393

import requests

394

from rtree.index import Index, Property, CustomStorage

395

396

class HTTPStorage(CustomStorage):

397

"""HTTP-based storage backend for distributed indexing."""

398

399

def __init__(self, base_url, auth_token=None):

400

super().__init__()

401

self.base_url = base_url.rstrip('/')

402

self.auth_token = auth_token

403

self.headers = {}

404

if auth_token:

405

self.headers['Authorization'] = f'Bearer {auth_token}'

406

407

def create(self, returnError):

408

"""Initialize remote storage."""

409

try:

410

response = requests.post(

411

f"{self.base_url}/storage/create",

412

headers=self.headers

413

)

414

if response.status_code == 200:

415

returnError.value = self.NoError

416

else:

417

returnError.value = self.IllegalStateError

418

except Exception:

419

returnError.value = self.IllegalStateError

420

421

def destroy(self, returnError):

422

"""Cleanup remote storage."""

423

try:

424

response = requests.delete(

425

f"{self.base_url}/storage",

426

headers=self.headers

427

)

428

returnError.value = self.NoError

429

except Exception:

430

returnError.value = self.IllegalStateError

431

432

def loadByteArray(self, page, returnError):

433

"""Load page from remote storage."""

434

try:

435

response = requests.get(

436

f"{self.base_url}/storage/pages/{page}",

437

headers=self.headers

438

)

439

if response.status_code == 200:

440

returnError.value = self.NoError

441

return response.text

442

elif response.status_code == 404:

443

returnError.value = self.InvalidPageError

444

return None

445

else:

446

returnError.value = self.IllegalStateError

447

return None

448

except Exception:

449

returnError.value = self.IllegalStateError

450

return None

451

452

def storeByteArray(self, page, data, returnError):

453

"""Store page to remote storage."""

454

try:

455

response = requests.put(

456

f"{self.base_url}/storage/pages/{page}",

457

data=data,

458

headers=self.headers

459

)

460

if response.status_code in (200, 201):

461

returnError.value = self.NoError

462

else:

463

returnError.value = self.IllegalStateError

464

except Exception:

465

returnError.value = self.IllegalStateError

466

467

def deleteByteArray(self, page, returnError):

468

"""Delete page from remote storage."""

469

try:

470

response = requests.delete(

471

f"{self.base_url}/storage/pages/{page}",

472

headers=self.headers

473

)

474

if response.status_code in (200, 204):

475

returnError.value = self.NoError

476

elif response.status_code == 404:

477

returnError.value = self.InvalidPageError

478

else:

479

returnError.value = self.IllegalStateError

480

except Exception:

481

returnError.value = self.IllegalStateError

482

483

def flush(self, returnError):

484

"""Flush pending operations."""

485

try:

486

response = requests.post(

487

f"{self.base_url}/storage/flush",

488

headers=self.headers

489

)

490

returnError.value = self.NoError

491

except Exception:

492

returnError.value = self.IllegalStateError

493

494

@property

495

def hasData(self):

496

try:

497

response = requests.head(

498

f"{self.base_url}/storage",

499

headers=self.headers

500

)

501

return response.status_code == 200

502

except Exception:

503

return False

504

505

# Use HTTP storage

506

storage = HTTPStorage("https://api.spatial-service.com", "your-auth-token")

507

prop = Property(storage=RT_Custom)

508

storage.registerCallbacks(prop)

509

510

idx = Index(properties=prop, storage=storage)

511

```

512

513

### Cached Storage Implementation

514

515

```python

516

import tempfile

517

import os

518

from rtree.index import Index, Property, CustomStorage

519

520

class CachedFileStorage(CustomStorage):

521

"""File storage with in-memory caching."""

522

523

def __init__(self, base_dir=None, cache_size=1000):

524

super().__init__()

525

self.base_dir = base_dir or tempfile.mkdtemp()

526

self.cache = {}

527

self.cache_size = cache_size

528

self.access_order = []

529

530

def _cache_key(self, page):

531

return f"page_{page}"

532

533

def _evict_cache(self):

534

"""Evict oldest entries if cache is full."""

535

while len(self.cache) >= self.cache_size:

536

oldest = self.access_order.pop(0)

537

if oldest in self.cache:

538

del self.cache[oldest]

539

540

def _update_access(self, key):

541

"""Update access order for LRU eviction."""

542

if key in self.access_order:

543

self.access_order.remove(key)

544

self.access_order.append(key)

545

546

def create(self, returnError):

547

"""Initialize file storage."""

548

try:

549

os.makedirs(self.base_dir, exist_ok=True)

550

returnError.value = self.NoError

551

except Exception:

552

returnError.value = self.IllegalStateError

553

554

def destroy(self, returnError):

555

"""Cleanup file storage."""

556

try:

557

self.cache.clear()

558

self.access_order.clear()

559

returnError.value = self.NoError

560

except Exception:

561

returnError.value = self.IllegalStateError

562

563

def loadByteArray(self, page, returnError):

564

"""Load page with caching."""

565

cache_key = self._cache_key(page)

566

567

# Check cache first

568

if cache_key in self.cache:

569

self._update_access(cache_key)

570

returnError.value = self.NoError

571

return self.cache[cache_key]

572

573

# Load from file

574

try:

575

file_path = os.path.join(self.base_dir, f"page_{page}.dat")

576

if os.path.exists(file_path):

577

with open(file_path, 'r') as f:

578

data = f.read()

579

580

# Cache the data

581

self._evict_cache()

582

self.cache[cache_key] = data

583

self._update_access(cache_key)

584

585

returnError.value = self.NoError

586

return data

587

else:

588

returnError.value = self.InvalidPageError

589

return None

590

except Exception:

591

returnError.value = self.IllegalStateError

592

return None

593

594

def storeByteArray(self, page, data, returnError):

595

"""Store page with caching."""

596

try:

597

# Write to file

598

file_path = os.path.join(self.base_dir, f"page_{page}.dat")

599

with open(file_path, 'w') as f:

600

f.write(data)

601

602

# Update cache

603

cache_key = self._cache_key(page)

604

self._evict_cache()

605

self.cache[cache_key] = data

606

self._update_access(cache_key)

607

608

returnError.value = self.NoError

609

except Exception:

610

returnError.value = self.IllegalStateError

611

612

def deleteByteArray(self, page, returnError):

613

"""Delete page and remove from cache."""

614

try:

615

file_path = os.path.join(self.base_dir, f"page_{page}.dat")

616

if os.path.exists(file_path):

617

os.remove(file_path)

618

619

# Remove from cache

620

cache_key = self._cache_key(page)

621

if cache_key in self.cache:

622

del self.cache[cache_key]

623

self.access_order.remove(cache_key)

624

625

returnError.value = self.NoError

626

else:

627

returnError.value = self.InvalidPageError

628

except Exception:

629

returnError.value = self.IllegalStateError

630

631

def flush(self, returnError):

632

"""Flush operation (files are written immediately)."""

633

returnError.value = self.NoError

634

635

@property

636

def hasData(self):

637

try:

638

return len(os.listdir(self.base_dir)) > 0

639

except Exception:

640

return False

641

642

# Use cached file storage

643

storage = CachedFileStorage("/tmp/rtree_cache", cache_size=500)

644

prop = Property(storage=RT_Custom)

645

storage.registerCallbacks(prop)

646

647

idx = Index(properties=prop, storage=storage)

648

```

649

650

## Type Definitions

651

652

```python { .api }

653

class CustomStorageCallbacks:

654

"""C callback functions for custom storage (ctypes.Structure)."""

655

# Internal structure for C API integration

656

# Used by registerCallbacks() to configure storage backend

657

```

658

659

## Constants

660

661

```python { .api }

662

# Error codes for custom storage

663

NoError = 0

664

InvalidPageError = 1

665

IllegalStateError = 2

666

667

# Special page identifiers

668

EmptyPage = -0x1

669

NewPage = -0x1

670

```