0
# Controllers and Commands
1
2
The controller system provides command organization and sub-command functionality using an argparse-based framework. Controllers enable organizing CLI functionality into logical groups with nested commands and parameter handling.
3
4
## Capabilities
5
6
### Controller Base Class
7
8
Base controller class that all application controllers should inherit from. Provides the foundation for command organization and argument handling.
9
10
```python { .api }
11
class Controller:
12
"""
13
Base controller class for organizing CLI commands.
14
15
Controllers group related commands together and provide a structured
16
way to organize application functionality. Each controller can have
17
multiple commands exposed through decorated methods.
18
"""
19
20
def __init__(self, **kw: Any) -> None:
21
"""
22
Initialize the controller.
23
24
Args:
25
**kw: Additional keyword arguments
26
"""
27
28
@property
29
def _meta(self) -> Any:
30
"""Controller meta-data configuration object."""
31
```
32
33
### Expose Decorator
34
35
Decorator for exposing controller methods as CLI commands. The exposed methods become available as sub-commands when the application runs.
36
37
```python { .api }
38
def expose(
39
hide: bool = False,
40
arguments: List[ArgparseArgumentType] = [],
41
label: str = None,
42
**parser_options: Any
43
) -> Callable:
44
"""
45
Decorator that exposes controller methods as CLI commands.
46
47
Args:
48
hide: Whether to hide the command from help output
49
arguments: List of argparse argument definitions
50
label: Override command name (defaults to method name)
51
**parser_options: Additional argparse subparser options (help, aliases, etc.)
52
53
Returns:
54
Decorated function that becomes a CLI command
55
"""
56
57
# Alias for expose decorator
58
ex = expose
59
```
60
61
### Controller Meta Configuration
62
63
Controller behavior is controlled through the Meta class which defines controller properties and command handling options.
64
65
```python { .api }
66
class Meta:
67
"""
68
Controller meta-data configuration.
69
70
Controls controller behavior including label, stacked commands,
71
and argument handling.
72
"""
73
74
label: str = None
75
"""Controller label/name"""
76
77
stacked_on: str = None
78
"""Stack this controller on top of another controller"""
79
80
stacked_type: str = 'embedded'
81
"""Stacking type: 'embedded' or 'nested'"""
82
83
description: str = None
84
"""Controller description for help text"""
85
86
usage: str = None
87
"""Custom usage string for help text"""
88
89
help: str = None
90
"""Help text for the controller"""
91
92
aliases: List[str] = []
93
"""List of controller aliases"""
94
95
arguments: List[Tuple[List[str], Dict[str, Any]]] = []
96
"""List of controller-level arguments"""
97
```
98
99
## Usage Examples
100
101
### Basic Controller
102
103
```python
104
from cement import App, Controller, ex
105
106
class BaseController(Controller):
107
class Meta:
108
label = 'base'
109
description = 'Base controller for application commands'
110
111
@ex(help='display application information')
112
def info(self):
113
"""Display application information."""
114
print('MyApp v1.0.0')
115
print('A sample cement application')
116
117
@ex(
118
help='greet someone',
119
arguments=[
120
(['name'], {'help': 'name to greet'}),
121
(['--uppercase', '-u'], {
122
'help': 'print in uppercase',
123
'action': 'store_true'
124
})
125
]
126
)
127
def greet(self):
128
"""Greet someone by name."""
129
name = self.app.pargs.name
130
greeting = f'Hello {name}!'
131
132
if self.app.pargs.uppercase:
133
greeting = greeting.upper()
134
135
print(greeting)
136
137
class MyApp(App):
138
class Meta:
139
label = 'myapp'
140
base_controller = 'base'
141
handlers = [BaseController]
142
143
with MyApp() as app:
144
app.run()
145
```
146
147
### Stacked Controllers
148
149
```python
150
from cement import App, Controller, ex
151
152
class BaseController(Controller):
153
class Meta:
154
label = 'base'
155
156
@ex(help='base command')
157
def default(self):
158
print('Base controller default command')
159
160
class DatabaseController(Controller):
161
class Meta:
162
label = 'database'
163
stacked_on = 'base'
164
stacked_type = 'nested'
165
description = 'Database management commands'
166
167
@ex(help='initialize database')
168
def init(self):
169
print('Initializing database...')
170
171
@ex(help='migrate database')
172
def migrate(self):
173
print('Running migrations...')
174
175
class UserController(Controller):
176
class Meta:
177
label = 'user'
178
stacked_on = 'base'
179
stacked_type = 'nested'
180
description = 'User management commands'
181
182
@ex(
183
help='create a new user',
184
arguments=[
185
(['username'], {'help': 'username for new user'}),
186
(['--email'], {'help': 'email address'})
187
]
188
)
189
def create(self):
190
username = self.app.pargs.username
191
email = getattr(self.app.pargs, 'email', None)
192
print(f'Creating user: {username}')
193
if email:
194
print(f'Email: {email}')
195
196
class MyApp(App):
197
class Meta:
198
label = 'myapp'
199
base_controller = 'base'
200
handlers = [
201
BaseController,
202
DatabaseController,
203
UserController
204
]
205
206
with MyApp() as app:
207
app.run()
208
209
# Usage:
210
# myapp info # Base controller
211
# myapp database init # Database controller
212
# myapp database migrate # Database controller
213
# myapp user create john # User controller
214
# myapp user create jane --email jane@example.com
215
```
216
217
### Controller with Arguments
218
219
```python
220
from cement import App, Controller, ex
221
222
class BaseController(Controller):
223
class Meta:
224
label = 'base'
225
arguments = [
226
(['--verbose', '-v'], {
227
'help': 'verbose output',
228
'action': 'store_true'
229
}),
230
(['--config'], {
231
'help': 'configuration file path',
232
'dest': 'config_file'
233
})
234
]
235
236
def _default(self):
237
"""Default command when no sub-command is specified."""
238
if self.app.pargs.verbose:
239
print('Verbose mode enabled')
240
241
if hasattr(self.app.pargs, 'config_file') and self.app.pargs.config_file:
242
print(f'Using config file: {self.app.pargs.config_file}')
243
244
print('Default command executed')
245
246
@ex(
247
help='process files',
248
arguments=[
249
(['files'], {
250
'nargs': '+',
251
'help': 'files to process'
252
}),
253
(['--output', '-o'], {
254
'help': 'output directory',
255
'default': './output'
256
}),
257
(['--format'], {
258
'choices': ['json', 'yaml', 'xml'],
259
'default': 'json',
260
'help': 'output format'
261
})
262
]
263
)
264
def process(self):
265
"""Process multiple files."""
266
files = self.app.pargs.files
267
output_dir = self.app.pargs.output
268
format_type = self.app.pargs.format
269
270
print(f'Processing {len(files)} files')
271
print(f'Output directory: {output_dir}')
272
print(f'Output format: {format_type}')
273
274
for file_path in files:
275
print(f'Processing: {file_path}')
276
277
class MyApp(App):
278
class Meta:
279
label = 'myapp'
280
base_controller = 'base'
281
handlers = [BaseController]
282
283
with MyApp() as app:
284
app.run()
285
286
# Usage:
287
# myapp --verbose
288
# myapp --config /etc/myapp.conf
289
# myapp process file1.txt file2.txt --output /tmp --format yaml
290
```
291
292
### Command Aliases
293
294
```python
295
from cement import App, Controller, ex
296
297
class BaseController(Controller):
298
class Meta:
299
label = 'base'
300
301
@ex(
302
help='display status information',
303
aliases=['stat', 'st']
304
)
305
def status(self):
306
"""Display application status."""
307
print('Application is running')
308
309
@ex(
310
help='restart the application',
311
aliases=['reboot', 'reload']
312
)
313
def restart(self):
314
"""Restart the application."""
315
print('Restarting application...')
316
317
class MyApp(App):
318
class Meta:
319
label = 'myapp'
320
base_controller = 'base'
321
handlers = [BaseController]
322
323
with MyApp() as app:
324
app.run()
325
326
# All of these commands are equivalent:
327
# myapp status
328
# myapp stat
329
# myapp st
330
```
331
332
### Hidden Commands
333
334
```python
335
from cement import App, Controller, ex
336
337
class BaseController(Controller):
338
class Meta:
339
label = 'base'
340
341
@ex(help='public command')
342
def public_cmd(self):
343
"""This command appears in help."""
344
print('Public command executed')
345
346
@ex(
347
help='hidden command for debugging',
348
hide=True
349
)
350
def debug_cmd(self):
351
"""This command is hidden from help but still callable."""
352
print('Debug command executed')
353
354
class MyApp(App):
355
class Meta:
356
label = 'myapp'
357
base_controller = 'base'
358
handlers = [BaseController]
359
360
with MyApp() as app:
361
app.run()
362
363
# myapp debug_cmd still works, but won't appear in --help
364
```
365
366
### Accessing Application Context
367
368
```python
369
from cement import App, Controller, ex
370
371
class BaseController(Controller):
372
class Meta:
373
label = 'base'
374
375
@ex(help='demonstrate app context access')
376
def demo(self):
377
"""Demonstrate accessing application context."""
378
# Access application configuration
379
debug_mode = self.app.config.get('myapp', 'debug')
380
print(f'Debug mode: {debug_mode}')
381
382
# Access logging
383
self.app.log.info('Demo command executed')
384
385
# Access parsed arguments
386
if hasattr(self.app.pargs, 'verbose'):
387
print(f'Verbose: {self.app.pargs.verbose}')
388
389
# Render output using output handler
390
data = {'message': 'Hello from demo command', 'timestamp': '2023-01-01'}
391
output = self.app.render(data)
392
print(output)
393
394
class MyApp(App):
395
class Meta:
396
label = 'myapp'
397
base_controller = 'base'
398
handlers = [BaseController]
399
400
with MyApp() as app:
401
app.run()
402
```