or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

active-directory.mdasync-frameworks.mdconnection-pooling.mdcore-ldap.mdindex.mdldif-support.mdutilities-errors.md

async-frameworks.mddocs/

0

# Asynchronous Framework Support

1

2

Native integration with multiple Python async frameworks including asyncio, gevent, tornado, and trio. Each framework provides specialized connection classes optimized for their respective event loops and concurrency models.

3

4

## Capabilities

5

6

### AsyncIO Support

7

8

Native Python asyncio integration with async/await syntax and asyncio event loop integration.

9

10

```python { .api }

11

from bonsai.asyncio import AIOLDAPConnection, AIOConnectionPool

12

13

class AIOLDAPConnection(LDAPConnection):

14

def __init__(self, client: LDAPClient, loop=None) -> None:

15

"""

16

Initialize asyncio LDAP connection.

17

18

Parameters:

19

- client: LDAPClient configuration object

20

- loop: asyncio event loop (uses current loop if None)

21

"""

22

23

async def __aenter__(self) -> "AIOLDAPConnection":

24

"""Async context manager entry point."""

25

26

async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:

27

"""Async context manager exit point."""

28

29

async def search(

30

self,

31

base: Optional[Union[str, LDAPDN]] = None,

32

scope: Optional[Union[LDAPSearchScope, int]] = None,

33

filter_exp: Optional[str] = None,

34

attrlist: Optional[List[str]] = None,

35

timeout: Optional[float] = None,

36

sizelimit: int = 0,

37

attrsonly: bool = False,

38

sort_order: Optional[List[str]] = None,

39

page_size: int = 0,

40

) -> List[LDAPEntry]:

41

"""

42

Async search LDAP directory for entries.

43

44

Parameters: Same as LDAPConnection.search()

45

46

Returns:

47

List of LDAPEntry objects matching search criteria

48

"""

49

50

async def add(self, entry: LDAPEntry, timeout: Optional[float] = None) -> None:

51

"""

52

Async add entry to LDAP directory.

53

54

Parameters:

55

- entry: LDAPEntry object to add

56

- timeout: Operation timeout in seconds

57

"""

58

59

async def modify(self, entry: LDAPEntry, timeout: Optional[float] = None) -> None:

60

"""

61

Async modify existing entry in LDAP directory.

62

63

Parameters:

64

- entry: LDAPEntry object with modifications

65

- timeout: Operation timeout in seconds

66

"""

67

68

async def delete(

69

self,

70

dname: Union[str, LDAPDN],

71

timeout: Optional[float] = None,

72

recursive: bool = False,

73

) -> None:

74

"""

75

Async delete entry from LDAP directory.

76

77

Parameters:

78

- dname: Distinguished name of entry to delete

79

- timeout: Operation timeout in seconds

80

- recursive: Delete entry and all children recursively

81

"""

82

83

async def rename(

84

self,

85

dn: Union[str, LDAPDN],

86

newrdn: str,

87

new_superior: Optional[Union[str, LDAPDN]] = None,

88

delete_old_rdn: bool = True,

89

timeout: Optional[float] = None,

90

) -> None:

91

"""

92

Async rename/move entry in LDAP directory.

93

94

Parameters: Same as LDAPConnection.rename()

95

"""

96

97

async def modify_password(

98

self,

99

user: Optional[Union[str, LDAPDN]] = None,

100

new_password: Optional[str] = None,

101

old_password: Optional[str] = None,

102

timeout: Optional[float] = None,

103

) -> str:

104

"""

105

Async modify user password.

106

107

Parameters: Same as LDAPConnection.modify_password()

108

109

Returns:

110

New password if server-generated

111

"""

112

113

async def open(self, timeout: Optional[float] = None) -> "AIOLDAPConnection":

114

"""

115

Async open connection to LDAP server.

116

117

Parameters:

118

- timeout: Connection timeout in seconds

119

120

Returns:

121

Self for method chaining

122

"""

123

124

class AIOConnectionPool:

125

def __init__(

126

self,

127

client: LDAPClient,

128

minconn: int = 1,

129

maxconn: int = 10,

130

loop=None

131

) -> None:

132

"""

133

Initialize asyncio connection pool.

134

135

Parameters:

136

- client: LDAPClient configuration

137

- minconn: Minimum connections to maintain

138

- maxconn: Maximum connections allowed

139

- loop: asyncio event loop

140

"""

141

142

async def get(self, timeout: Optional[float] = None) -> AIOLDAPConnection:

143

"""Get connection from pool (awaitable)."""

144

145

async def put(self, conn: AIOLDAPConnection) -> None:

146

"""Return connection to pool (awaitable)."""

147

148

async def spawn(self, *args, **kwargs):

149

"""

150

Async context manager for getting and returning pooled connections.

151

152

Usage:

153

async with pool.spawn() as conn:

154

# Use connection

155

results = await conn.search(...)

156

# Connection automatically returned to pool

157

"""

158

159

async def close(self) -> None:

160

"""Close all connections in pool."""

161

162

@property

163

def closed(self) -> bool:

164

"""Whether the pool is closed."""

165

166

@property

167

def idle_connection(self) -> int:

168

"""Number of idle connections."""

169

170

@property

171

def shared_connection(self) -> int:

172

"""Number of connections in use."""

173

174

@property

175

def max_connection(self) -> int:

176

"""Maximum number of connections allowed."""

177

178

@property

179

def min_connection(self) -> int:

180

"""Minimum number of connections to maintain."""

181

```

182

183

### Gevent Support

184

185

Integration with gevent greenlets for cooperative concurrency.

186

187

```python { .api }

188

from bonsai.gevent import GeventLDAPConnection

189

190

class GeventLDAPConnection(LDAPConnection):

191

def __init__(self, client: LDAPClient) -> None:

192

"""

193

Initialize gevent LDAP connection.

194

195

Parameters:

196

- client: LDAPClient configuration object

197

"""

198

199

def search(

200

self,

201

base: Optional[Union[str, LDAPDN]] = None,

202

scope: Optional[Union[LDAPSearchScope, int]] = None,

203

filter_exp: Optional[str] = None,

204

attrlist: Optional[List[str]] = None,

205

timeout: Optional[float] = None,

206

sizelimit: int = 0,

207

attrsonly: bool = False,

208

sort_order: Optional[List[str]] = None,

209

page_size: int = 0,

210

) -> List[LDAPEntry]:

211

"""

212

Search LDAP directory (yields to other greenlets during I/O).

213

214

Parameters: Same as LDAPConnection.search()

215

216

Returns:

217

List of LDAPEntry objects

218

"""

219

220

def add(self, entry: LDAPEntry, timeout: Optional[float] = None) -> None:

221

"""Add entry (yields during I/O)."""

222

223

def modify(self, entry: LDAPEntry, timeout: Optional[float] = None) -> None:

224

"""Modify entry (yields during I/O)."""

225

226

def delete(

227

self,

228

dname: Union[str, LDAPDN],

229

timeout: Optional[float] = None,

230

recursive: bool = False,

231

) -> None:

232

"""Delete entry (yields during I/O)."""

233

234

def rename(

235

self,

236

dn: Union[str, LDAPDN],

237

newrdn: str,

238

new_superior: Optional[Union[str, LDAPDN]] = None,

239

delete_old_rdn: bool = True,

240

timeout: Optional[float] = None,

241

) -> None:

242

"""Rename entry (yields during I/O)."""

243

244

def modify_password(

245

self,

246

user: Optional[Union[str, LDAPDN]] = None,

247

new_password: Optional[str] = None,

248

old_password: Optional[str] = None,

249

timeout: Optional[float] = None,

250

) -> str:

251

"""Modify password (yields during I/O)."""

252

253

def open(self, timeout: Optional[float] = None) -> "GeventLDAPConnection":

254

"""Open connection (yields during I/O)."""

255

```

256

257

### Tornado Support

258

259

Integration with Tornado's event loop and Future-based async model.

260

261

```python { .api }

262

from bonsai.tornado import TornadoLDAPConnection

263

from tornado.concurrent import Future

264

265

class TornadoLDAPConnection(LDAPConnection):

266

def __init__(self, client: LDAPClient, ioloop=None) -> None:

267

"""

268

Initialize Tornado LDAP connection.

269

270

Parameters:

271

- client: LDAPClient configuration object

272

- ioloop: Tornado IOLoop instance

273

"""

274

275

def search(

276

self,

277

base: Optional[Union[str, LDAPDN]] = None,

278

scope: Optional[Union[LDAPSearchScope, int]] = None,

279

filter_exp: Optional[str] = None,

280

attrlist: Optional[List[str]] = None,

281

timeout: Optional[float] = None,

282

sizelimit: int = 0,

283

attrsonly: bool = False,

284

sort_order: Optional[List[str]] = None,

285

page_size: int = 0,

286

) -> Future[List[LDAPEntry]]:

287

"""

288

Search LDAP directory returning Future.

289

290

Parameters: Same as LDAPConnection.search()

291

292

Returns:

293

Future[List[LDAPEntry]] that resolves to search results

294

"""

295

296

def add(self, entry: LDAPEntry, timeout: Optional[float] = None) -> Future[None]:

297

"""

298

Add entry returning Future.

299

300

Parameters:

301

- entry: LDAPEntry object to add

302

- timeout: Operation timeout in seconds

303

304

Returns:

305

Future[None] that resolves when operation completes

306

"""

307

308

def modify(self, entry: LDAPEntry, timeout: Optional[float] = None) -> Future[None]:

309

"""Modify entry returning Future."""

310

311

def delete(

312

self,

313

dname: Union[str, LDAPDN],

314

timeout: Optional[float] = None,

315

recursive: bool = False,

316

) -> Future[None]:

317

"""Delete entry returning Future."""

318

319

def rename(

320

self,

321

dn: Union[str, LDAPDN],

322

newrdn: str,

323

new_superior: Optional[Union[str, LDAPDN]] = None,

324

delete_old_rdn: bool = True,

325

timeout: Optional[float] = None,

326

) -> Future[None]:

327

"""Rename entry returning Future."""

328

329

def modify_password(

330

self,

331

user: Optional[Union[str, LDAPDN]] = None,

332

new_password: Optional[str] = None,

333

old_password: Optional[str] = None,

334

timeout: Optional[float] = None,

335

) -> Future[str]:

336

"""

337

Modify password returning Future.

338

339

Returns:

340

Future[str] that resolves to new password if server-generated

341

"""

342

343

def open(self, timeout: Optional[float] = None) -> Future["TornadoLDAPConnection"]:

344

"""

345

Open connection returning Future.

346

347

Returns:

348

Future[TornadoLDAPConnection] that resolves to self

349

"""

350

```

351

352

### Trio Support

353

354

Integration with Trio's structured concurrency model.

355

356

```python { .api }

357

from bonsai.trio import TrioLDAPConnection

358

359

class TrioLDAPConnection(LDAPConnection):

360

def __init__(self, client: LDAPClient) -> None:

361

"""

362

Initialize Trio LDAP connection.

363

364

Parameters:

365

- client: LDAPClient configuration object

366

"""

367

368

async def search(

369

self,

370

base: Optional[Union[str, LDAPDN]] = None,

371

scope: Optional[Union[LDAPSearchScope, int]] = None,

372

filter_exp: Optional[str] = None,

373

attrlist: Optional[List[str]] = None,

374

timeout: Optional[float] = None,

375

sizelimit: int = 0,

376

attrsonly: bool = False,

377

sort_order: Optional[List[str]] = None,

378

page_size: int = 0,

379

) -> List[LDAPEntry]:

380

"""

381

Async search LDAP directory using Trio.

382

383

Parameters: Same as LDAPConnection.search()

384

385

Returns:

386

List of LDAPEntry objects

387

"""

388

389

async def add(self, entry: LDAPEntry, timeout: Optional[float] = None) -> None:

390

"""Async add entry using Trio."""

391

392

async def modify(self, entry: LDAPEntry, timeout: Optional[float] = None) -> None:

393

"""Async modify entry using Trio."""

394

395

async def delete(

396

self,

397

dname: Union[str, LDAPDN],

398

timeout: Optional[float] = None,

399

recursive: bool = False,

400

) -> None:

401

"""Async delete entry using Trio."""

402

403

async def rename(

404

self,

405

dn: Union[str, LDAPDN],

406

newrdn: str,

407

new_superior: Optional[Union[str, LDAPDN]] = None,

408

delete_old_rdn: bool = True,

409

timeout: Optional[float] = None,

410

) -> None:

411

"""Async rename entry using Trio."""

412

413

async def modify_password(

414

self,

415

user: Optional[Union[str, LDAPDN]] = None,

416

new_password: Optional[str] = None,

417

old_password: Optional[str] = None,

418

timeout: Optional[float] = None,

419

) -> str:

420

"""Async modify password using Trio."""

421

422

async def open(self, timeout: Optional[float] = None) -> "TrioLDAPConnection":

423

"""Async open connection using Trio."""

424

```

425

426

## Usage Examples

427

428

### AsyncIO Usage

429

430

```python

431

import asyncio

432

from bonsai import LDAPClient

433

from bonsai.asyncio import AIOLDAPConnection

434

435

async def async_ldap_operations():

436

# Configure client for async operations

437

client = LDAPClient("ldap://localhost")

438

client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")

439

440

# Create async connection

441

async with client.connect(is_async=True) as conn:

442

# Async search

443

results = await conn.search(

444

"dc=example,dc=com",

445

2, # SUBTREE

446

"(objectClass=person)"

447

)

448

449

print(f"Found {len(results)} entries")

450

451

# Async operations on entries

452

for entry in results:

453

print(f"Processing: {entry.dn}")

454

# Could perform other async operations here

455

456

# Create and add new entry

457

from bonsai import LDAPEntry

458

new_entry = LDAPEntry("cn=async-user,dc=example,dc=com")

459

new_entry['objectClass'] = ['person', 'organizationalPerson']

460

new_entry['cn'] = 'async-user'

461

new_entry['sn'] = 'User'

462

463

await conn.add(new_entry)

464

print("Entry added successfully")

465

466

# Run async function

467

asyncio.run(async_ldap_operations())

468

```

469

470

### Gevent Usage

471

472

```python

473

from bonsai import LDAPClient

474

import gevent

475

from gevent import socket

476

477

def gevent_ldap_operations():

478

# Configure client for gevent

479

client = LDAPClient("ldap://localhost")

480

client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")

481

482

# Connect using gevent connection

483

with client.connect() as conn:

484

# Operations automatically yield to other greenlets during I/O

485

results = conn.search("dc=example,dc=com", 2, "(objectClass=person)")

486

print(f"Found {len(results)} entries")

487

488

def worker_greenlet(worker_id):

489

print(f"Worker {worker_id} starting")

490

gevent_ldap_operations()

491

print(f"Worker {worker_id} finished")

492

493

# Spawn multiple greenlets

494

greenlets = []

495

for i in range(5):

496

greenlet = gevent.spawn(worker_greenlet, i)

497

greenlets.append(greenlet)

498

499

# Wait for all to complete

500

gevent.joinall(greenlets)

501

```

502

503

### Tornado Usage

504

505

```python

506

from tornado import gen, ioloop

507

from bonsai import LDAPClient

508

509

@gen.coroutine

510

def tornado_ldap_operations():

511

# Configure client for tornado

512

client = LDAPClient("ldap://localhost")

513

client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")

514

515

conn = client.connect(is_async=True) # Returns TornadoLDAPConnection

516

yield conn.open()

517

518

try:

519

# All operations return Futures

520

search_future = conn.search("dc=example,dc=com", 2, "(objectClass=person)")

521

results = yield search_future

522

print(f"Found {len(results)} entries")

523

524

# Chain operations with Futures

525

from bonsai import LDAPEntry

526

new_entry = LDAPEntry("cn=tornado-user,dc=example,dc=com")

527

new_entry['objectClass'] = ['person']

528

new_entry['cn'] = 'tornado-user'

529

new_entry['sn'] = 'User'

530

531

add_future = conn.add(new_entry)

532

yield add_future

533

print("Entry added successfully")

534

535

finally:

536

conn.close()

537

538

# Run with Tornado IOLoop

539

if __name__ == "__main__":

540

ioloop.IOLoop.current().run_sync(tornado_ldap_operations)

541

```

542

543

### Trio Usage

544

545

```python

546

import trio

547

from bonsai import LDAPClient

548

549

async def trio_ldap_operations():

550

# Configure client for trio

551

client = LDAPClient("ldap://localhost")

552

client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")

553

554

conn = client.connect(is_async=True) # Returns TrioLDAPConnection

555

await conn.open()

556

557

try:

558

# Structured concurrency with nurseries

559

async with trio.open_nursery() as nursery:

560

# Search in background

561

nursery.start_soon(perform_search, conn)

562

nursery.start_soon(perform_add, conn)

563

# Both operations run concurrently

564

565

finally:

566

conn.close()

567

568

async def perform_search(conn):

569

results = await conn.search("dc=example,dc=com", 2, "(objectClass=person)")

570

print(f"Search found {len(results)} entries")

571

572

async def perform_add(conn):

573

from bonsai import LDAPEntry

574

new_entry = LDAPEntry("cn=trio-user,dc=example,dc=com")

575

new_entry['objectClass'] = ['person']

576

new_entry['cn'] = 'trio-user'

577

new_entry['sn'] = 'User'

578

579

await conn.add(new_entry)

580

print("Entry added successfully")

581

582

# Run with trio

583

trio.run(trio_ldap_operations)

584

```

585

586

### Connection Pool with AsyncIO

587

588

```python

589

import asyncio

590

from bonsai import LDAPClient

591

from bonsai.asyncio import AIOConnectionPool

592

593

async def pooled_operations():

594

client = LDAPClient("ldap://localhost")

595

client.set_credentials("SIMPLE", user="cn=admin,dc=example,dc=com", password="secret")

596

597

# Create connection pool

598

pool = AIOConnectionPool(client, minconn=2, maxconn=10)

599

600

try:

601

# Get connection from pool

602

conn = await pool.get()

603

604

# Perform operations

605

results = await conn.search("dc=example,dc=com", 2, "(objectClass=person)")

606

print(f"Found {len(results)} entries")

607

608

# Return connection to pool

609

await pool.put(conn)

610

611

finally:

612

# Close all connections in pool

613

await pool.close()

614

615

asyncio.run(pooled_operations())

616

```