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
```