0
# Interface and Handler System
1
2
The interface and handler system provides the foundation for cement's pluggable architecture. Interfaces define contracts for functionality while handlers provide concrete implementations, enabling customizable behavior across all framework components.
3
4
## Capabilities
5
6
### Interface Base Class
7
8
Base class for defining framework interfaces that specify contracts for functionality.
9
10
```python { .api }
11
class Interface:
12
"""
13
Base interface class that all cement interfaces should inherit from.
14
15
Interfaces define contracts and specifications for functionality
16
that handlers must implement. They provide the blueprint for
17
pluggable components in the cement framework.
18
"""
19
20
def __init__(self, **kw: Any) -> None:
21
"""
22
Initialize the interface.
23
24
Args:
25
**kw: Additional keyword arguments
26
"""
27
28
def _validate(self) -> None:
29
"""
30
Perform validation to ensure proper interface definition.
31
32
This method should be overridden by interface implementations
33
to validate interface-specific requirements.
34
"""
35
36
@property
37
def _meta(self) -> Any:
38
"""Interface meta-data configuration object."""
39
```
40
41
### Handler Base Class
42
43
Base class for implementing interface contracts through concrete handler implementations.
44
45
```python { .api }
46
class Handler:
47
"""
48
Base handler class that all cement handlers should inherit from.
49
50
Handlers provide concrete implementations of interface contracts.
51
They contain the actual functionality that interfaces define.
52
"""
53
54
def __init__(self, **kw: Any) -> None:
55
"""
56
Initialize the handler.
57
58
Args:
59
**kw: Additional keyword arguments
60
"""
61
62
def _validate(self) -> None:
63
"""
64
Perform validation to ensure proper handler implementation.
65
66
This method should be overridden by handler implementations
67
to validate handler-specific requirements and interface compliance.
68
"""
69
70
@property
71
def _meta(self) -> Any:
72
"""Handler meta-data configuration object."""
73
```
74
75
### Interface Manager
76
77
Manages the interface system including interface definition, registration, and retrieval.
78
79
```python { .api }
80
class InterfaceManager:
81
"""
82
Manages the interface system for defining and retrieving interfaces.
83
84
The interface manager provides centralized control over interface
85
registration and lookup functionality.
86
"""
87
88
def define(self, interface: Interface) -> None:
89
"""
90
Define a new interface in the system.
91
92
Args:
93
interface: Interface class to define
94
"""
95
96
def defined(self, interface: str) -> bool:
97
"""
98
Check if an interface is defined.
99
100
Args:
101
interface: Interface name to check
102
103
Returns:
104
True if interface is defined, False otherwise
105
"""
106
107
def list(self) -> List[str]:
108
"""
109
Get list of all defined interfaces.
110
111
Returns:
112
List of interface names
113
"""
114
115
def get(self, interface: str) -> Interface:
116
"""
117
Get an interface by name.
118
119
Args:
120
interface: Interface name to retrieve
121
122
Returns:
123
Interface class
124
125
Raises:
126
InterfaceError: If interface is not found
127
"""
128
```
129
130
### Handler Manager
131
132
Manages the handler system including handler registration, resolution, and retrieval.
133
134
```python { .api }
135
class HandlerManager:
136
"""
137
Manages the handler system for registering and resolving handlers.
138
139
The handler manager provides centralized control over handler
140
registration, lookup, and resolution functionality.
141
"""
142
143
def register(self, handler: Handler) -> None:
144
"""
145
Register a handler in the system.
146
147
Args:
148
handler: Handler class to register
149
"""
150
151
def registered(self, interface: str, handler: str) -> bool:
152
"""
153
Check if a handler is registered for an interface.
154
155
Args:
156
interface: Interface name
157
handler: Handler name
158
159
Returns:
160
True if handler is registered, False otherwise
161
"""
162
163
def list(self, interface: str) -> List[str]:
164
"""
165
Get list of registered handlers for an interface.
166
167
Args:
168
interface: Interface name
169
170
Returns:
171
List of handler names for the interface
172
"""
173
174
def get(self, interface: str, handler: str) -> Handler:
175
"""
176
Get a handler instance by interface and handler name.
177
178
Args:
179
interface: Interface name
180
handler: Handler name
181
182
Returns:
183
Handler instance
184
185
Raises:
186
FrameworkError: If handler is not found
187
"""
188
189
def resolve(self, interface: str, handler: str) -> Handler:
190
"""
191
Resolve and return a handler instance.
192
193
Args:
194
interface: Interface name
195
handler: Handler name
196
197
Returns:
198
Resolved handler instance
199
200
Raises:
201
FrameworkError: If handler cannot be resolved
202
"""
203
```
204
205
### Interface Meta Configuration
206
207
Interface behavior is controlled through the Meta class that defines interface properties.
208
209
```python { .api }
210
class Meta:
211
"""
212
Interface meta-data configuration.
213
214
Controls interface behavior and properties.
215
"""
216
217
interface: str = None
218
"""The string identifier of this interface"""
219
```
220
221
### Handler Meta Configuration
222
223
Handler behavior is controlled through the Meta class that defines handler properties and interface association.
224
225
```python { .api }
226
class Meta:
227
"""
228
Handler meta-data configuration.
229
230
Controls handler behavior, interface association, and configuration.
231
"""
232
233
label: str = None
234
"""The string identifier of this handler"""
235
236
interface: str = None
237
"""The interface that this handler implements"""
238
239
config_section: str = None
240
"""Configuration section to merge config_defaults with"""
241
242
config_defaults: Dict[str, Any] = None
243
"""Default configuration dictionary"""
244
245
overridable: bool = False
246
"""Whether this handler can be overridden"""
247
```
248
249
## Usage Examples
250
251
### Defining a Custom Interface
252
253
```python
254
from cement import Interface
255
256
class DatabaseInterface(Interface):
257
"""
258
Interface for database operations.
259
260
Defines the contract that database handlers must implement.
261
"""
262
263
class Meta:
264
interface = 'database'
265
266
def connect(self, **kwargs):
267
"""
268
Connect to database.
269
270
This method must be implemented by handlers.
271
"""
272
raise NotImplementedError
273
274
def execute(self, query, params=None):
275
"""
276
Execute a database query.
277
278
Args:
279
query: SQL query string
280
params: Query parameters
281
282
This method must be implemented by handlers.
283
"""
284
raise NotImplementedError
285
286
def close(self):
287
"""
288
Close database connection.
289
290
This method must be implemented by handlers.
291
"""
292
raise NotImplementedError
293
```
294
295
### Implementing a Handler
296
297
```python
298
from cement import Handler
299
import sqlite3
300
301
class SQLiteHandler(Handler, DatabaseInterface):
302
"""
303
SQLite implementation of the database interface.
304
"""
305
306
class Meta:
307
label = 'sqlite'
308
interface = 'database'
309
config_defaults = {
310
'database_file': './app.db',
311
'timeout': 30
312
}
313
314
def __init__(self, **kw):
315
super().__init__(**kw)
316
self.connection = None
317
318
def connect(self, **kwargs):
319
"""Connect to SQLite database."""
320
db_file = kwargs.get('database_file', self._meta.config_defaults['database_file'])
321
timeout = kwargs.get('timeout', self._meta.config_defaults['timeout'])
322
323
self.connection = sqlite3.connect(db_file, timeout=timeout)
324
return self.connection
325
326
def execute(self, query, params=None):
327
"""Execute SQLite query."""
328
if not self.connection:
329
raise FrameworkError("Database not connected")
330
331
cursor = self.connection.cursor()
332
if params:
333
cursor.execute(query, params)
334
else:
335
cursor.execute(query)
336
337
return cursor.fetchall()
338
339
def close(self):
340
"""Close SQLite connection."""
341
if self.connection:
342
self.connection.close()
343
self.connection = None
344
```
345
346
### Using Interfaces and Handlers in an Application
347
348
```python
349
from cement import App, Controller, ex
350
351
class BaseController(Controller):
352
class Meta:
353
label = 'base'
354
355
@ex(help='test database operations')
356
def test_db(self):
357
"""Test database operations."""
358
# Get database handler through the app
359
db = self.app.handler.get('database', 'sqlite')()
360
361
try:
362
# Connect to database
363
db.connect(database_file='./test.db')
364
365
# Create table
366
db.execute('''
367
CREATE TABLE IF NOT EXISTS users (
368
id INTEGER PRIMARY KEY,
369
name TEXT,
370
email TEXT
371
)
372
''')
373
374
# Insert data
375
db.execute(
376
'INSERT INTO users (name, email) VALUES (?, ?)',
377
('John Doe', 'john@example.com')
378
)
379
380
# Query data
381
results = db.execute('SELECT * FROM users')
382
for row in results:
383
print(f'User: {row[1]}, Email: {row[2]}')
384
385
finally:
386
db.close()
387
388
class MyApp(App):
389
class Meta:
390
label = 'myapp'
391
base_controller = 'base'
392
handlers = [
393
BaseController,
394
SQLiteHandler
395
]
396
397
# Register the custom interface
398
def load(app):
399
app.interface.define(DatabaseInterface)
400
401
with MyApp() as app:
402
load(app)
403
app.run()
404
```
405
406
### Multiple Handler Implementations
407
408
```python
409
from cement import Handler
410
import psycopg2
411
412
class PostgreSQLHandler(Handler, DatabaseInterface):
413
"""
414
PostgreSQL implementation of the database interface.
415
"""
416
417
class Meta:
418
label = 'postgresql'
419
interface = 'database'
420
config_defaults = {
421
'host': 'localhost',
422
'port': 5432,
423
'database': 'myapp',
424
'user': 'postgres',
425
'password': ''
426
}
427
428
def __init__(self, **kw):
429
super().__init__(**kw)
430
self.connection = None
431
432
def connect(self, **kwargs):
433
"""Connect to PostgreSQL database."""
434
config = self._meta.config_defaults.copy()
435
config.update(kwargs)
436
437
self.connection = psycopg2.connect(
438
host=config['host'],
439
port=config['port'],
440
database=config['database'],
441
user=config['user'],
442
password=config['password']
443
)
444
return self.connection
445
446
def execute(self, query, params=None):
447
"""Execute PostgreSQL query."""
448
if not self.connection:
449
raise FrameworkError("Database not connected")
450
451
cursor = self.connection.cursor()
452
cursor.execute(query, params)
453
return cursor.fetchall()
454
455
def close(self):
456
"""Close PostgreSQL connection."""
457
if self.connection:
458
self.connection.close()
459
self.connection = None
460
461
class MyApp(App):
462
class Meta:
463
label = 'myapp'
464
base_controller = 'base'
465
handlers = [
466
BaseController,
467
SQLiteHandler,
468
PostgreSQLHandler
469
]
470
471
def load(app):
472
app.interface.define(DatabaseInterface)
473
474
with MyApp() as app:
475
load(app)
476
477
# Can choose which database handler to use
478
sqlite_db = app.handler.get('database', 'sqlite')()
479
postgres_db = app.handler.get('database', 'postgresql')()
480
481
app.run()
482
```
483
484
### Handler Override Options
485
486
```python
487
from cement import App, init_defaults
488
489
CONFIG = init_defaults('myapp')
490
CONFIG['myapp']['database_handler'] = 'sqlite' # Default handler
491
492
class MyApp(App):
493
class Meta:
494
label = 'myapp'
495
config_defaults = CONFIG
496
handler_override_options = {
497
'database': (['--database-handler'], {
498
'help': 'database handler to use',
499
'choices': ['sqlite', 'postgresql']
500
})
501
}
502
handlers = [
503
SQLiteHandler,
504
PostgreSQLHandler
505
]
506
507
def load(app):
508
app.interface.define(DatabaseInterface)
509
510
with MyApp() as app:
511
load(app)
512
513
# Handler can be overridden via command line:
514
# myapp --database-handler postgresql command
515
516
# Get the configured/overridden handler
517
handler_name = app.config.get('myapp', 'database_handler')
518
db = app.handler.get('database', handler_name)()
519
520
app.run()
521
```
522
523
### Checking Handler Registration
524
525
```python
526
from cement import App
527
528
class MyApp(App):
529
class Meta:
530
label = 'myapp'
531
handlers = [SQLiteHandler, PostgreSQLHandler]
532
533
def load(app):
534
app.interface.define(DatabaseInterface)
535
536
with MyApp() as app:
537
load(app)
538
app.setup()
539
540
# Check if interface is defined
541
if app.interface.defined('database'):
542
print("Database interface is defined")
543
544
# Check if handlers are registered
545
if app.handler.registered('database', 'sqlite'):
546
print("SQLite handler is registered")
547
548
if app.handler.registered('database', 'postgresql'):
549
print("PostgreSQL handler is registered")
550
551
# List all handlers for interface
552
handlers = app.handler.list('database')
553
print(f"Available database handlers: {handlers}")
554
```