foundation.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. """Cement core foundation module."""
  2. import re
  3. import os
  4. import sys
  5. import signal
  6. import copy
  7. import platform
  8. from time import sleep
  9. from ..core import backend, exc, log, config, plugin, interface
  10. from ..core import output, extension, arg, controller, meta, cache, mail
  11. from ..core.handler import HandlerManager
  12. from ..core.hook import HookManager
  13. from ..utils.misc import is_true, minimal_logger
  14. from ..utils import fs
  15. if sys.version_info[0] >= 3:
  16. from imp import reload # pragma: nocover
  17. LOG = minimal_logger(__name__)
  18. if platform.system() == 'Windows':
  19. SIGNALS = [signal.SIGTERM, signal.SIGINT]
  20. else:
  21. SIGNALS = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]
  22. def add_handler_override_options(app):
  23. """
  24. This is a ``post_setup`` hook that adds the handler override options to
  25. the argument parser
  26. :param app: The application object.
  27. """
  28. if app._meta.handler_override_options is None:
  29. return
  30. for i in app._meta.handler_override_options:
  31. if i not in interface.list():
  32. LOG.debug("interface '%s'" % i +
  33. " is not defined, can not override handlers")
  34. continue
  35. if len(app.handler.list(i)) > 1:
  36. handlers = []
  37. for h in app.handler.list(i):
  38. handlers.append(h())
  39. choices = [x._meta.label
  40. for x in handlers
  41. if x._meta.overridable is True]
  42. # don't display the option if no handlers are overridable
  43. if not len(choices) > 0:
  44. LOG.debug("no handlers are overridable within the " +
  45. "%s interface" % i)
  46. continue
  47. # override things that we need to control
  48. argument_kw = app._meta.handler_override_options[i][1]
  49. argument_kw['dest'] = '%s_handler_override' % i
  50. argument_kw['action'] = 'store'
  51. argument_kw['choices'] = choices
  52. app.args.add_argument(
  53. *app._meta.handler_override_options[i][0],
  54. **app._meta.handler_override_options[i][1]
  55. )
  56. def handler_override(app):
  57. """
  58. This is a ``post_argument_parsing`` hook that overrides a configured
  59. handler if defined in ``CementApp.Meta.handler_override_options`` and
  60. the option is passed at command line with a valid handler label.
  61. :param app: The application object.
  62. """
  63. if app._meta.handler_override_options is None:
  64. return
  65. for i in app._meta.handler_override_options.keys():
  66. if not hasattr(app.pargs, '%s_handler_override' % i):
  67. continue
  68. elif getattr(app.pargs, '%s_handler_override' % i) is None:
  69. continue
  70. else:
  71. # get the argument value from command line
  72. argument = getattr(app.pargs, '%s_handler_override' % i)
  73. setattr(app._meta, '%s_handler' % i, argument)
  74. # and then re-setup the handler
  75. getattr(app, '_setup_%s_handler' % i)()
  76. def cement_signal_handler(signum, frame):
  77. """
  78. Catch a signal, run the 'signal' hook, and then raise an exception
  79. allowing the app to handle logic elsewhere.
  80. :param signum: The signal number
  81. :param frame: The signal frame.
  82. :raises: cement.core.exc.CaughtSignal
  83. """
  84. LOG.debug('Caught signal %s' % signum)
  85. # FIXME: Maybe this isn't ideal... purhaps make
  86. # CementApp.Meta.signal_handler a decorator that take the app object
  87. # and wraps/returns the actually signal handler?
  88. for f_global in frame.f_globals.values():
  89. if isinstance(f_global, CementApp):
  90. app = f_global
  91. for res in app.hook.run('signal', app, signum, frame):
  92. pass # pragma: nocover
  93. raise exc.CaughtSignal(signum, frame)
  94. class CementApp(meta.MetaMixin):
  95. """
  96. The primary class to build applications from.
  97. Usage:
  98. The following is the simplest CementApp:
  99. .. code-block:: python
  100. from cement.core.foundation import CementApp
  101. with CementApp('helloworld') as app:
  102. app.run()
  103. Alternatively, the above could be written as:
  104. .. code-block:: python
  105. from cement.core.foundation import CementApp
  106. app = foundation.CementApp('helloworld')
  107. app.setup()
  108. app.run()
  109. app.close()
  110. A more advanced example looks like:
  111. .. code-block:: python
  112. from cement.core.foundation import CementApp
  113. from cement.core.controller import CementBaseController, expose
  114. class MyController(CementBaseController):
  115. class Meta:
  116. label = 'base'
  117. arguments = [
  118. ( ['-f', '--foo'], dict(help='Notorious foo option') ),
  119. ]
  120. config_defaults = dict(
  121. debug=False,
  122. some_config_param='some_value',
  123. )
  124. @expose(help='This is the default command', hide=True)
  125. def default(self):
  126. print('Hello World')
  127. class MyApp(CementApp):
  128. class Meta:
  129. label = 'helloworld'
  130. extensions = ['daemon','json',]
  131. base_controller = MyController
  132. with MyApp() as app:
  133. app.run()
  134. """
  135. class Meta:
  136. """
  137. Application meta-data (can also be passed as keyword arguments to the
  138. parent class).
  139. """
  140. label = None
  141. """
  142. The name of the application. This should be the common name as you
  143. would see and use at the command line. For example 'helloworld', or
  144. 'my-awesome-app'.
  145. """
  146. debug = False
  147. """
  148. Used internally, and should not be used by developers. This is set
  149. to `True` if `--debug` is passed at command line."""
  150. exit_on_close = False
  151. """
  152. Whether or not to call ``sys.exit()`` when ``close()`` is called.
  153. The default is ``False``, however if ``True`` then the app will call
  154. ``sys.exit(X)`` where ``X`` is ``self.exit_code``.
  155. """
  156. config_files = None
  157. """
  158. List of config files to parse.
  159. Note: Though Meta.config_section defaults to None, Cement will
  160. set this to a default list based on Meta.label (or in other words,
  161. the name of the application). This will equate to:
  162. .. code-block:: python
  163. ['/etc/<app_label>/<app_label>.conf',
  164. '~/.<app_label>.conf',
  165. '~/.<app_label>/config']
  166. Files are loaded in order, and have precedence in order. Therefore,
  167. the last configuration loaded has precedence (and overwrites settings
  168. loaded from previous configuration files).
  169. """
  170. plugins = []
  171. """
  172. A list of plugins to load. This is generally considered bad
  173. practice since plugins should be dynamically enabled/disabled
  174. via a plugin config file.
  175. """
  176. plugin_config_dirs = None
  177. """
  178. A list of directory paths where plugin config files can be found.
  179. Files must end in `.conf` or they will be ignored.
  180. Note: Though ``CementApp.Meta.plugin_config_dirs`` is ``None``, Cement
  181. will set this to a default list based on ``CementApp.Meta.label``.
  182. This will equate to:
  183. .. code-block:: python
  184. ['/etc/<app_label>/plugins.d', '~/.<app_label>/plugin.d']
  185. Files are loaded in order, and have precedence in that order.
  186. Therefore, the last configuration loaded has precedence (and
  187. overwrites settings loaded from previous configuration files).
  188. """
  189. plugin_config_dir = None
  190. """
  191. A directory path where plugin config files can be found. Files
  192. must end in `.conf`. By default, this setting is also overridden
  193. by the ``[<app_label>] -> plugin_config_dir`` config setting parsed in
  194. any of the application configuration files.
  195. If set, this item will be **appended** to
  196. ``CementApp.Meta.plugin_config_dirs`` so that it's settings will have
  197. presedence over other configuration files.
  198. In general, this setting should not be defined by the developer, as it
  199. is primarily used to allow the end-user to define a
  200. ``plugin_config_dir`` without completely trumping the hard-coded list
  201. of default ``plugin_config_dirs`` defined by the app/developer.
  202. """
  203. plugin_bootstrap = None
  204. """
  205. A python package (dotted import path) where plugin code can be
  206. loaded from. This is generally something like ``myapp.plugins``
  207. where a plugin file would live at ``myapp/plugins/myplugin.py``.
  208. This provides a facility for applications that have builtin plugins
  209. that ship with the applications source code and live in the same
  210. Python module.
  211. Note: Though the meta default is ``None``, Cement will set this to
  212. ``<app_label>.plugins`` if not set.
  213. """
  214. plugin_dirs = None
  215. """
  216. A list of directory paths where plugin code (modules) can be loaded
  217. from.
  218. Note: Though ``CementApp.Meta.plugin_dirs`` is None, Cement will set
  219. this to a default list based on ``CementApp.Meta.label`` if not set.
  220. This will equate to:
  221. .. code-block:: python
  222. ['~/.<app_label>/plugins', '/usr/lib/<app_label>/plugins']
  223. Modules are attempted to be loaded in order, and will stop loading
  224. once a plugin is successfully loaded from a directory. Therefore
  225. this is the oposite of configuration file loading, in that here the
  226. first has precedence.
  227. """
  228. plugin_dir = None
  229. """
  230. A directory path where plugin code (modules) can be loaded from.
  231. By default, this setting is also overridden by the
  232. ``[<app_label>] -> plugin_dir`` config setting parsed in any of the
  233. application configuration files.
  234. If set, this item will be **prepended** to ``Meta.plugin_dirs`` so
  235. that a users defined ``plugin_dir`` has precedence over others.
  236. In general, this setting should not be defined by the developer, as it
  237. is primarily used to allow the end-user to define a
  238. ``plugin_dir`` without completely trumping the hard-coded list
  239. of default ``plugin_dirs`` defined by the app/developer.
  240. """
  241. argv = None
  242. """
  243. A list of arguments to use for parsing command line arguments
  244. and options.
  245. Note: Though Meta.argv defaults to None, Cement will set this to
  246. ``list(sys.argv[1:])`` if no argv is set in Meta during setup().
  247. """
  248. arguments_override_config = False
  249. """
  250. A boolean to toggle whether command line arguments should
  251. override configuration values if the argument name matches the
  252. config key. I.e. --foo=bar would override config['myapp']['foo'].
  253. This is different from ``override_arguments`` in that if
  254. ``arguments_override_config`` is ``True``, then all arguments will
  255. override (you don't have to list them all).
  256. """
  257. override_arguments = ['debug']
  258. """
  259. List of arguments that override their configuration counter-part.
  260. For example, if ``--debug`` is passed (and it's config value is
  261. ``debug``) then the ``debug`` key of all configuration sections will
  262. be overridden by the value of the command line option (``True`` in
  263. this example).
  264. This is different from ``arguments_override_config`` in that this is
  265. a selective list of specific arguments to override the config with
  266. (and not all arguments that match the config). This list will take
  267. affect whether ``arguments_override_config`` is ``True`` or ``False``.
  268. """
  269. core_handler_override_options = dict(
  270. output=(['-o'], dict(help='output handler')),
  271. )
  272. """
  273. Similar to ``CementApp.Meta.handler_override_options`` but these are
  274. the core defaults required by Cement. This dictionary can be
  275. overridden by ``CementApp.Meta.handler_override_options`` (when they
  276. are merged together).
  277. """
  278. handler_override_options = {}
  279. """
  280. Dictionary of handler override options that will be added to the
  281. argument parser, and allow the end-user to override handlers. Useful
  282. for interfaces that have multiple uses within the same application
  283. (for example: Output Handler (json, yaml, etc) or maybe a Cloud
  284. Provider Handler (rackspace, digitalocean, amazon, etc).
  285. This dictionary will merge with
  286. ``CementApp.Meta.core_handler_override_options`` but this one has
  287. precedence.
  288. Dictionary Format:
  289. .. code-block:: text
  290. <interface_name> = (option_arguments, help_text)
  291. See ``CementApp.Meta.core_handler_override_options`` for an example
  292. of what this should look like.
  293. Note, if set to ``None`` then no options will be defined, and the
  294. ``CementApp.Meta.core_meta_override_options`` will be ignore (not
  295. recommended as some extensions rely on this feature).
  296. """
  297. config_section = None
  298. """
  299. The base configuration section for the application.
  300. Note: Though Meta.config_section defaults to None, Cement will
  301. set this to the value of Meta.label (or in other words, the name
  302. of the application).
  303. """
  304. config_defaults = None
  305. """Default configuration dictionary. Must be of type 'dict'."""
  306. catch_signals = SIGNALS
  307. """
  308. List of signals to catch, and raise exc.CaughtSignal for.
  309. Can be set to None to disable signal handling.
  310. """
  311. signal_handler = cement_signal_handler
  312. """A function that is called to handle any caught signals."""
  313. config_handler = 'configparser'
  314. """
  315. A handler class that implements the IConfig interface.
  316. """
  317. mail_handler = 'dummy'
  318. """
  319. A handler class that implements the IMail interface.
  320. """
  321. extension_handler = 'cement'
  322. """
  323. A handler class that implements the IExtension interface.
  324. """
  325. log_handler = 'logging'
  326. """
  327. A handler class that implements the ILog interface.
  328. """
  329. plugin_handler = 'cement'
  330. """
  331. A handler class that implements the IPlugin interface.
  332. """
  333. argument_handler = 'argparse'
  334. """
  335. A handler class that implements the IArgument interface.
  336. """
  337. output_handler = 'dummy'
  338. """
  339. A handler class that implements the IOutput interface.
  340. """
  341. cache_handler = None
  342. """
  343. A handler class that implements the ICache interface.
  344. """
  345. base_controller = None
  346. """
  347. This is the base application controller. If a controller is set,
  348. runtime operations are passed to the controller for command
  349. dispatch and argument parsing when CementApp.run() is called.
  350. Note that cement will automatically set the `base_controller` to a
  351. registered controller whose label is 'base' (only if `base_controller`
  352. is not currently set).
  353. """
  354. extensions = []
  355. """List of additional framework extensions to load."""
  356. bootstrap = None
  357. """
  358. A bootstrapping module to load after app creation, and before
  359. app.setup() is called. This is useful for larger applications
  360. that need to offload their bootstrapping code such as registering
  361. hooks/handlers/etc to another file.
  362. This must be a dotted python module path.
  363. I.e. 'myapp.bootstrap' (myapp/bootstrap.py). Cement will then
  364. import the module, and if the module has a 'load()' function, that
  365. will also be called. Essentially, this is the same as an
  366. extension or plugin, but as a facility for the application itself
  367. to bootstrap 'hardcoded' application code. It is also called
  368. before plugins are loaded.
  369. """
  370. core_extensions = [
  371. 'cement.ext.ext_dummy',
  372. 'cement.ext.ext_smtp',
  373. 'cement.ext.ext_plugin',
  374. 'cement.ext.ext_configparser',
  375. 'cement.ext.ext_logging',
  376. 'cement.ext.ext_argparse',
  377. ]
  378. """
  379. List of Cement core extensions. These are generally required by
  380. Cement and should only be modified if you know what you're
  381. doing. Use 'extensions' to add to this list, rather than
  382. overriding core extensions. That said if you want to prune down
  383. your application, you can remove core extensions if they are
  384. not necessary (for example if using your own log handler
  385. extension you likely don't want/need LoggingLogHandler to be
  386. registered).
  387. """
  388. core_meta_override = [
  389. 'debug',
  390. 'plugin_config_dir',
  391. 'plugin_dir',
  392. 'ignore_deprecation_warnings',
  393. 'template_dir',
  394. 'mail_handler',
  395. 'cache_handler',
  396. 'log_handler',
  397. 'output_handler',
  398. ]
  399. """
  400. List of meta options that can/will be overridden by config options
  401. of the '[base]' config section (where [base] is the base
  402. configuration section of the application which is determined by
  403. Meta.config_section but defaults to Meta.label). These overrides
  404. are required by the framework to function properly and should not
  405. be used by end user (developers) unless you really know what
  406. you're doing. To add your own extended meta overrides please use
  407. 'meta_override'.
  408. """
  409. meta_override = []
  410. """
  411. List of meta options that can/will be overridden by config options
  412. of the '[base]' config section (where [base] is the
  413. base configuration section of the application which is determined
  414. by Meta.config_section but defaults to Meta.label).
  415. """
  416. ignore_deprecation_warnings = False
  417. """Disable deprecation warnings from being logged by Cement."""
  418. template_module = None
  419. """
  420. A python package (dotted import path) where template files can be
  421. loaded from. This is generally something like ``myapp.templates``
  422. where a plugin file would live at ``myapp/templates/mytemplate.txt``.
  423. Templates are first loaded from ``CementApp.Meta.template_dirs``, and
  424. and secondly from ``CementApp.Meta.template_module``. The
  425. ``template_dirs`` setting has presedence.
  426. """
  427. template_dirs = None
  428. """
  429. A list of directory paths where template files can be loaded
  430. from.
  431. Note: Though ``CementApp.Meta.template_dirs`` defaults to ``None``,
  432. Cement will set this to a default list based on
  433. ``CementApp.Meta.label``. This will equate to:
  434. .. code-block:: python
  435. ['~/.<app_label>/templates', '/usr/lib/<app_label>/templates']
  436. Templates are attempted to be loaded in order, and will stop loading
  437. once a template is successfully loaded from a directory.
  438. """
  439. template_dir = None
  440. """
  441. A directory path where template files can be loaded from. By default,
  442. this setting is also overridden by the
  443. ``[<app_label>] -> template_dir`` config setting parsed in any of the
  444. application configuration files .
  445. If set, this item will be **prepended** to
  446. ``CementApp.Meta.template_dirs`` (giving it precedence over other
  447. ``template_dirs``.
  448. """
  449. framework_logging = True
  450. """
  451. Whether or not to enable Cement framework logging. This is separate
  452. from the application log, and is generally used for debugging issues
  453. with the framework and/or extensions primarily in development.
  454. This option is overridden by the environment variable
  455. `CEMENT_FRAMEWORK_LOGGING`. Therefore, if in production you do not
  456. want the Cement framework log enabled, you can set this option to
  457. ``False`` but override it in your environment by doing something like
  458. ``export CEMENT_FRAMEWORK_LOGGING=1`` in your shell whenever you need
  459. it enabled.
  460. """
  461. define_hooks = []
  462. """
  463. List of hook definitions (label). Will be passed to
  464. ``self.hook.define(<hook_label>)``. Must be a list of strings.
  465. I.e. ``['my_custom_hook', 'some_other_hook']``
  466. """
  467. hooks = []
  468. """
  469. List of hooks to register when the app is created. Will be passed to
  470. ``self.hook.register(<hook_label>, <hook_func>)``. Must be a list of
  471. tuples in the form of ``(<hook_label>, <hook_func>)``.
  472. I.e. ``[('post_argument_parsing', my_hook_func)]``.
  473. """
  474. define_handlers = []
  475. """
  476. List of interfaces classes to define handlers. Must be a list of
  477. uninstantiated interface classes.
  478. I.e. ``['MyCustomInterface', 'SomeOtherInterface']``
  479. """
  480. handlers = []
  481. """
  482. List of handler classes to register. Will be passed to
  483. ``handler.register(<handler_class>)``. Must be a list of
  484. uninstantiated handler classes.
  485. I.e. ``[MyCustomHandler, SomeOtherHandler]``
  486. """
  487. use_backend_globals = True
  488. """
  489. This is a backward compatibility feature. Cement 2.x.x
  490. relies on several global variables hidden in ``cement.core.backend``
  491. used for things like storing hooks and handlers. Future versions of
  492. Cement will no longer use this mechanism, however in order to maintain
  493. backward compatibility this is still the default. By disabling this
  494. feature allows multiple instances of CementApp to be created
  495. from within the same runtime space without clobbering eachothers
  496. hooks/handers/etc.
  497. Be warned that use of third-party extensions might break as they were
  498. built using backend globals, and probably have no idea this feature
  499. has changed or exists.
  500. """
  501. def __init__(self, label=None, **kw):
  502. super(CementApp, self).__init__(**kw)
  503. # disable framework logging?
  504. if 'CEMENT_FRAMEWORK_LOGGING' not in os.environ.keys():
  505. if self._meta.framework_logging is True:
  506. os.environ['CEMENT_FRAMEWORK_LOGGING'] = '1'
  507. else:
  508. os.environ['CEMENT_FRAMEWORK_LOGGING'] = '0'
  509. # for convenience we translate this to _meta
  510. if label:
  511. self._meta.label = label
  512. self._validate_label()
  513. self._loaded_bootstrap = None
  514. self._parsed_args = None
  515. self._last_rendered = None
  516. self.__saved_stdout__ = None
  517. self.__saved_stderr__ = None
  518. self.handler = None
  519. self.hook = None
  520. self.exit_code = 0
  521. self.ext = None
  522. self.config = None
  523. self.log = None
  524. self.plugin = None
  525. self.args = None
  526. self.output = None
  527. self.controller = None
  528. self.cache = None
  529. self.mail = None
  530. # setup argv... this has to happen before lay_cement()
  531. if self._meta.argv is None:
  532. self._meta.argv = list(sys.argv[1:])
  533. # hack for command line --debug
  534. if '--debug' in self.argv:
  535. self._meta.debug = True
  536. # setup the cement framework
  537. self._lay_cement()
  538. @property
  539. def debug(self):
  540. """
  541. Returns boolean based on whether ``--debug`` was passed at command
  542. line or set via the application's configuration file.
  543. :returns: boolean
  544. """
  545. return self._meta.debug
  546. @property
  547. def argv(self):
  548. """The arguments list that will be used when self.run() is called."""
  549. return self._meta.argv
  550. def extend(self, member_name, member_object):
  551. """
  552. Extend the CementApp() object with additional functions/classes such
  553. as 'app.my_custom_function()', etc. It provides an interface for
  554. extensions to provide functionality that travel along with the
  555. application object.
  556. :param member_name: The name to attach the object to.
  557. :type member_name: ``str``
  558. :param member_object: The function or class object to attach to
  559. CementApp().
  560. :raises: cement.core.exc.FrameworkError
  561. """
  562. if hasattr(self, member_name):
  563. raise exc.FrameworkError("App member '%s' already exists!" %
  564. member_name)
  565. LOG.debug("extending appication with '.%s' (%s)" %
  566. (member_name, member_object))
  567. setattr(self, member_name, member_object)
  568. def _validate_label(self):
  569. if not self._meta.label:
  570. raise exc.FrameworkError("Application name missing.")
  571. # validate the name is ok
  572. ok = ['_', '-']
  573. for char in self._meta.label:
  574. if char in ok:
  575. continue
  576. if not char.isalnum():
  577. raise exc.FrameworkError(
  578. "App label can only contain alpha-numeric, dashes, " +
  579. "or underscores."
  580. )
  581. def setup(self):
  582. """
  583. This function wraps all '_setup' actons in one call. It is called
  584. before self.run(), allowing the application to be _setup but not
  585. executed (possibly letting the developer perform other actions
  586. before full execution.).
  587. All handlers should be instantiated and callable after setup is
  588. complete.
  589. """
  590. LOG.debug("now setting up the '%s' application" % self._meta.label)
  591. if self._meta.bootstrap is not None:
  592. LOG.debug("importing bootstrap code from %s" %
  593. self._meta.bootstrap)
  594. if self._meta.bootstrap not in sys.modules \
  595. or self._loaded_bootstrap is None:
  596. __import__(self._meta.bootstrap, globals(), locals(), [], 0)
  597. if hasattr(sys.modules[self._meta.bootstrap], 'load'):
  598. sys.modules[self._meta.bootstrap].load(self)
  599. self._loaded_bootstrap = sys.modules[self._meta.bootstrap]
  600. else:
  601. reload(self._loaded_bootstrap)
  602. for res in self.hook.run('pre_setup', self):
  603. pass
  604. self._setup_extension_handler()
  605. self._setup_signals()
  606. self._setup_config_handler()
  607. self._setup_mail_handler()
  608. self._setup_cache_handler()
  609. self._setup_log_handler()
  610. self._setup_plugin_handler()
  611. self._setup_arg_handler()
  612. self._setup_output_handler()
  613. self._setup_controllers()
  614. for res in self.hook.run('post_setup', self):
  615. pass
  616. def run(self):
  617. """
  618. This function wraps everything together (after self._setup() is
  619. called) to run the application.
  620. :returns: Returns the result of the executed controller function if
  621. a base controller is set and a controller function is called,
  622. otherwise ``None`` if no controller dispatched or no controller
  623. function was called.
  624. """
  625. return_val = None
  626. LOG.debug('running pre_run hook')
  627. for res in self.hook.run('pre_run', self):
  628. pass
  629. # If controller exists, then dispatch it
  630. if self.controller:
  631. return_val = self.controller._dispatch()
  632. else:
  633. self._parse_args()
  634. LOG.debug('running post_run hook')
  635. for res in self.hook.run('post_run', self):
  636. pass
  637. return return_val
  638. def run_forever(self, interval=1, tb=True):
  639. """
  640. This function wraps ``run()`` with an endless while loop. If any
  641. exception is encountered it will be logged and then the application
  642. will be reloaded.
  643. :param interval: The number of seconds to sleep before reloading the
  644. the appliction.
  645. :param tb: Whether or not to print traceback if exception occurs.
  646. :returns: It should never return.
  647. """
  648. if tb is True:
  649. import traceback
  650. while True:
  651. LOG.debug('inside run_forever() eternal loop')
  652. try:
  653. self.run()
  654. except Exception as e:
  655. self.log.fatal('Caught Exception: %s' % e)
  656. if tb is True:
  657. exc_type, exc_value, exc_traceback = sys.exc_info()
  658. traceback.print_exception(
  659. exc_type, exc_value, exc_traceback, limit=2,
  660. file=sys.stdout
  661. )
  662. sleep(interval)
  663. self.reload()
  664. def reload(self):
  665. """
  666. This function is useful for reloading a running applications, for
  667. example to reload configuration settings, etc.
  668. :returns: ``None``
  669. """
  670. LOG.debug('reloading the %s application' % self._meta.label)
  671. self.handler.__handlers__ = {}
  672. self.hook.__hooks__ = {}
  673. self._lay_cement()
  674. self.setup()
  675. def close(self, code=None):
  676. """
  677. Close the application. This runs the ``pre_close`` and ``post_close``
  678. hooks allowing plugins/extensions/etc to cleanup at the end of
  679. program execution.
  680. :param code: An exit code to exit with (``int``), if ``None`` is
  681. passed then exit with whatever ``self.exit_code`` is currently set
  682. to. Note: ``sys.exit()`` will only be called if
  683. ``CementApp.Meta.exit_on_close==True``.
  684. """
  685. for res in self.hook.run('pre_close', self):
  686. pass
  687. LOG.debug("closing the %s application" % self._meta.label)
  688. for res in self.hook.run('post_close', self):
  689. pass
  690. if code is not None:
  691. assert type(code) == int, \
  692. "Invalid exit status code (must be integer)"
  693. self.exit_code = code
  694. if self._meta.exit_on_close is True:
  695. sys.exit(self.exit_code)
  696. def render(self, data, template=None, out=sys.stdout, **kw):
  697. """
  698. This is a simple wrapper around self.output.render() which simply
  699. returns an empty string if no self.output handler is defined.
  700. :param data: The data dictionary to render.
  701. :param template: The template to render to. Default: None (some
  702. output handlers do not use templates).
  703. :param out: A file like object (sys.stdout, or actual file). Set to
  704. ``None`` is no output is desired (just render and return).
  705. Default: sys.stdout
  706. """
  707. for res in self.hook.run('pre_render', self, data):
  708. if not type(res) is dict:
  709. LOG.debug("pre_render hook did not return a dict().")
  710. else:
  711. data = res
  712. kw['template'] = template
  713. if self.output is None:
  714. LOG.debug('render() called, but no output handler defined.')
  715. out_text = ''
  716. else:
  717. out_text = self.output.render(data, **kw)
  718. for res in self.hook.run('post_render', self, out_text):
  719. if not type(res) is str:
  720. LOG.debug('post_render hook did not return a str()')
  721. else:
  722. out_text = str(res)
  723. if out is not None and not hasattr(out, 'write'):
  724. raise TypeError("Argument 'out' must be a 'file' like object")
  725. elif out is not None and out_text is None:
  726. LOG.debug('render() called but output text is None')
  727. elif out:
  728. out.write(out_text)
  729. self._last_rendered = (data, out_text)
  730. return out_text
  731. def get_last_rendered(self):
  732. """
  733. DEPRECATION WARNING: This function is deprecated as of Cement 2.1.3
  734. in favor of the `self.last_rendered` property, and will be removed in
  735. future versions of Cement.
  736. Return the (data, output_text) tuple of the last time self.render()
  737. was called.
  738. :returns: tuple (data, output_text)
  739. """
  740. if not is_true(self._meta.ignore_deprecation_warnings):
  741. self.log.warn("Cement Deprecation Warning: " +
  742. "CementApp.get_last_rendered() has been " +
  743. "deprecated, and will be removed in future " +
  744. "versions of Cement. You should use the " +
  745. "CementApp.last_rendered property instead.")
  746. return self._last_rendered
  747. @property
  748. def last_rendered(self):
  749. """
  750. Return the (data, output_text) tuple of the last time self.render() was
  751. called.
  752. :returns: tuple (data, output_text)
  753. """
  754. return self._last_rendered
  755. @property
  756. def pargs(self):
  757. """
  758. Returns the `parsed_args` object as returned by self.args.parse().
  759. """
  760. return self._parsed_args
  761. def add_arg(self, *args, **kw):
  762. """A shortcut for self.args.add_argument."""
  763. self.args.add_argument(*args, **kw)
  764. def _suppress_output(self):
  765. if self._meta.debug is True:
  766. LOG.debug('not suppressing console output because of debug mode')
  767. return
  768. LOG.debug('suppressing all console output')
  769. self.__saved_stdout__ = sys.stdout
  770. self.__saved_stderr__ = sys.stderr
  771. sys.stdout = open(os.devnull, 'w')
  772. sys.stderr = open(os.devnull, 'w')
  773. def _unsuppress_output(self):
  774. LOG.debug('unsuppressing all console output')
  775. sys.stdout = self.__saved_stdout__
  776. sys.stderr = self.__saved_stderr__
  777. def _lay_cement(self):
  778. """Initialize the framework."""
  779. LOG.debug("laying cement for the '%s' application" %
  780. self._meta.label)
  781. if '--debug' in self._meta.argv:
  782. self._meta.debug = True
  783. elif '--quiet' in self._meta.argv:
  784. self._suppress_output()
  785. # Forward/Backward compat, see Issue #311
  786. if self._meta.use_backend_globals:
  787. backend.__hooks__ = {}
  788. backend.__handlers__ = {}
  789. self.handler = HandlerManager(use_backend_globals=True)
  790. self.hook = HookManager(use_backend_globals=True)
  791. else:
  792. self.handler = HandlerManager(use_backend_globals=False)
  793. self.hook = HookManager(use_backend_globals=False)
  794. # define framework hooks
  795. self.hook.define('pre_setup')
  796. self.hook.define('post_setup')
  797. self.hook.define('pre_run')
  798. self.hook.define('post_run')
  799. self.hook.define('pre_argument_parsing')
  800. self.hook.define('post_argument_parsing')
  801. self.hook.define('pre_close')
  802. self.hook.define('post_close')
  803. self.hook.define('signal')
  804. self.hook.define('pre_render')
  805. self.hook.define('post_render')
  806. # define application hooks from meta
  807. for label in self._meta.define_hooks:
  808. self.hook.define(label)
  809. # register some built-in framework hooks
  810. self.hook.register(
  811. 'post_setup', add_handler_override_options, weight=-99)
  812. self.hook.register('post_argument_parsing',
  813. handler_override, weight=-99)
  814. # register application hooks from meta
  815. for label, func in self._meta.hooks:
  816. self.hook.register(label, func)
  817. # define and register handlers
  818. self.handler.define(extension.IExtension)
  819. self.handler.define(log.ILog)
  820. self.handler.define(config.IConfig)
  821. self.handler.define(mail.IMail)
  822. self.handler.define(plugin.IPlugin)
  823. self.handler.define(output.IOutput)
  824. self.handler.define(arg.IArgument)
  825. self.handler.define(controller.IController)
  826. self.handler.define(cache.ICache)
  827. # define application handlers
  828. for interface_class in self._meta.define_handlers:
  829. self.handler.define(interface_class)
  830. # extension handler is the only thing that can't be loaded... as,
  831. # well, an extension. ;)
  832. self.handler.register(extension.CementExtensionHandler)
  833. # register application handlers
  834. for handler_class in self._meta.handlers:
  835. self.handler.register(handler_class)
  836. def _parse_args(self):
  837. for res in self.hook.run('pre_argument_parsing', self):
  838. pass
  839. self._parsed_args = self.args.parse(self.argv)
  840. if self._meta.arguments_override_config is True:
  841. for member in dir(self._parsed_args):
  842. if member and member.startswith('_'):
  843. continue
  844. # don't override config values for options that weren't passed
  845. # or in otherwords are None
  846. elif getattr(self._parsed_args, member) is None:
  847. continue
  848. for section in self.config.get_sections():
  849. if member in self.config.keys(section):
  850. self.config.set(section, member,
  851. getattr(self._parsed_args, member))
  852. for member in self._meta.override_arguments:
  853. for section in self.config.get_sections():
  854. if member in self.config.keys(section):
  855. self.config.set(section, member,
  856. getattr(self._parsed_args, member))
  857. for res in self.hook.run('post_argument_parsing', self):
  858. pass
  859. def catch_signal(self, signum):
  860. """
  861. Add ``signum`` to the list of signals to catch and handle by Cement.
  862. :param signum: The signal number to catch. See Python ``signal``
  863. library.
  864. """
  865. LOG.debug("adding signal handler %s for signal %s" % (
  866. self._meta.signal_handler, signum)
  867. )
  868. signal.signal(signum, self._meta.signal_handler)
  869. def _setup_signals(self):
  870. if self._meta.catch_signals is None:
  871. LOG.debug("catch_signals=None... not handling any signals")
  872. return
  873. for signum in self._meta.catch_signals:
  874. self.catch_signal(signum)
  875. def _resolve_handler(self, handler_type, handler_def, raise_error=True):
  876. han = self.handler.resolve(handler_type, handler_def, raise_error)
  877. if han is not None:
  878. han._setup(self)
  879. return han
  880. def _setup_extension_handler(self):
  881. LOG.debug("setting up %s.extension handler" % self._meta.label)
  882. self.ext = self._resolve_handler('extension',
  883. self._meta.extension_handler)
  884. self.ext.load_extensions(self._meta.core_extensions)
  885. self.ext.load_extensions(self._meta.extensions)
  886. def _setup_config_handler(self):
  887. LOG.debug("setting up %s.config handler" % self._meta.label)
  888. self.config = self._resolve_handler('config',
  889. self._meta.config_handler)
  890. if self._meta.config_section is None:
  891. self._meta.config_section = self._meta.label
  892. self.config.add_section(self._meta.config_section)
  893. if self._meta.config_defaults is not None:
  894. self.config.merge(self._meta.config_defaults)
  895. if self._meta.config_files is None:
  896. label = self._meta.label
  897. self._meta.config_files = [
  898. os.path.join('/', 'etc', label, '%s.conf' % label),
  899. os.path.join(fs.HOME_DIR, '.%s.conf' % label),
  900. os.path.join(fs.HOME_DIR, '.%s' % label, 'config'),
  901. ]
  902. for _file in self._meta.config_files:
  903. self.config.parse_file(_file)
  904. self.validate_config()
  905. # hack for --debug
  906. if '--debug' in self.argv or self._meta.debug is True:
  907. self.config.set(self._meta.config_section, 'debug', True)
  908. # override select Meta via config
  909. base_dict = self.config.get_section_dict(self._meta.config_section)
  910. for key in base_dict:
  911. if key in self._meta.core_meta_override or \
  912. key in self._meta.meta_override:
  913. # kind of a hack for core_meta_override
  914. if key in ['debug']:
  915. setattr(self._meta, key, is_true(base_dict[key]))
  916. else:
  917. setattr(self._meta, key, base_dict[key])
  918. # load extensions from configuraton file
  919. if 'extensions' in self.config.keys(self._meta.label):
  920. exts = self.config.get(self._meta.label, 'extensions')
  921. # convert a comma-separated string to a list
  922. if type(exts) is str:
  923. ext_list = exts.split(',')
  924. # clean up extra space if they had it inbetween commas
  925. ext_list = [x.strip() for x in ext_list]
  926. # set the new extensions value in the config
  927. self.config.set(self._meta.label, 'extensions', ext_list)
  928. # otherwise, if it's a list (ConfigObj?)
  929. elif type(exts) is list:
  930. ext_list = exts
  931. for ext in ext_list:
  932. # load the extension
  933. self.ext.load_extension(ext)
  934. # add to meta data
  935. self._meta.extensions.append(ext)
  936. def _setup_mail_handler(self):
  937. LOG.debug("setting up %s.mail handler" % self._meta.label)
  938. self.mail = self._resolve_handler('mail',
  939. self._meta.mail_handler)
  940. def _setup_log_handler(self):
  941. LOG.debug("setting up %s.log handler" % self._meta.label)
  942. self.log = self._resolve_handler('log', self._meta.log_handler)
  943. def _setup_plugin_handler(self):
  944. LOG.debug("setting up %s.plugin handler" % self._meta.label)
  945. label = self._meta.label
  946. # plugin config dirs
  947. if self._meta.plugin_config_dirs is None:
  948. self._meta.plugin_config_dirs = [
  949. '/etc/%s/plugins.d/' % self._meta.label,
  950. os.path.join(fs.HOME_DIR, '.%s' % label, 'plugins.d'),
  951. ]
  952. config_dir = self._meta.plugin_config_dir
  953. if config_dir is not None:
  954. if config_dir not in self._meta.plugin_config_dirs:
  955. # append so that this config has precedence
  956. self._meta.plugin_config_dirs.append(config_dir)
  957. # plugin dirs
  958. if self._meta.plugin_dirs is None:
  959. self._meta.plugin_dirs = [
  960. os.path.join(fs.HOME_DIR, '.%s' % label, 'plugins'),
  961. '/usr/lib/%s/plugins' % self._meta.label,
  962. ]
  963. plugin_dir = self._meta.plugin_dir
  964. if plugin_dir is not None:
  965. if plugin_dir not in self._meta.plugin_dirs:
  966. # insert so that this dir has precedence
  967. self._meta.plugin_dirs.insert(0, plugin_dir)
  968. # plugin bootstrap
  969. if self._meta.plugin_bootstrap is None:
  970. self._meta.plugin_bootstrap = '%s.plugins' % self._meta.label
  971. self.plugin = self._resolve_handler('plugin',
  972. self._meta.plugin_handler)
  973. self.plugin.load_plugins(self._meta.plugins)
  974. self.plugin.load_plugins(self.plugin.get_enabled_plugins())
  975. def _setup_output_handler(self):
  976. if self._meta.output_handler is None:
  977. LOG.debug("no output handler defined, skipping.")
  978. return
  979. label = self._meta.label
  980. LOG.debug("setting up %s.output handler" % self._meta.label)
  981. self.output = self._resolve_handler('output',
  982. self._meta.output_handler,
  983. raise_error=False)
  984. # template module
  985. if self._meta.template_module is None:
  986. self._meta.template_module = '%s.templates' % label
  987. # template dirs
  988. if self._meta.template_dirs is None:
  989. self._meta.template_dirs = [
  990. os.path.join(fs.HOME_DIR, '.%s' % label, 'templates'),
  991. '/usr/lib/%s/templates' % label,
  992. ]
  993. template_dir = self._meta.template_dir
  994. if template_dir is not None:
  995. if template_dir not in self._meta.template_dirs:
  996. # insert so that this dir has precedence
  997. self._meta.template_dirs.insert(0, template_dir)
  998. def _setup_cache_handler(self):
  999. if self._meta.cache_handler is None:
  1000. LOG.debug("no cache handler defined, skipping.")
  1001. return
  1002. LOG.debug("setting up %s.cache handler" % self._meta.label)
  1003. self.cache = self._resolve_handler('cache',
  1004. self._meta.cache_handler,
  1005. raise_error=False)
  1006. def _setup_arg_handler(self):
  1007. LOG.debug("setting up %s.arg handler" % self._meta.label)
  1008. self.args = self._resolve_handler('argument',
  1009. self._meta.argument_handler)
  1010. self.args.prog = self._meta.label
  1011. self.args.add_argument('--debug', dest='debug',
  1012. action='store_true',
  1013. help='toggle debug output')
  1014. self.args.add_argument('--quiet', dest='suppress_output',
  1015. action='store_true',
  1016. help='suppress all output')
  1017. # merge handler override meta data
  1018. if self._meta.handler_override_options is not None:
  1019. # fucking long names... fuck. anyway, merge the core handler
  1020. # override options with developer defined options
  1021. core = self._meta.core_handler_override_options.copy()
  1022. dev = self._meta.handler_override_options.copy()
  1023. core.update(dev)
  1024. self._meta.handler_override_options = core
  1025. def _setup_controllers(self):
  1026. LOG.debug("setting up application controllers")
  1027. if self._meta.base_controller is not None:
  1028. cntr = self._resolve_handler('controller',
  1029. self._meta.base_controller)
  1030. self.controller = cntr
  1031. self._meta.base_controller = self.controller
  1032. elif self._meta.base_controller is None:
  1033. if self.handler.registered('controller', 'base'):
  1034. self.controller = self._resolve_handler('controller', 'base')
  1035. self._meta.base_controller = self.controller
  1036. # This is necessary for some backend usage
  1037. if self._meta.base_controller is not None:
  1038. if self._meta.base_controller._meta.label != 'base':
  1039. raise exc.FrameworkError("Base controllers must have " +
  1040. "a label of 'base'.")
  1041. def validate_config(self):
  1042. """
  1043. Validate application config settings.
  1044. Usage:
  1045. .. code-block:: python
  1046. import os
  1047. from cement.core import foundation
  1048. class MyApp(foundation.CementApp):
  1049. class Meta:
  1050. label = 'myapp'
  1051. def validate_config(self):
  1052. super(MyApp, self).validate_config()
  1053. # test that the log file directory exist, if not create it
  1054. logdir = os.path.dirname(self.config.get('log', 'file'))
  1055. if not os.path.exists(logdir):
  1056. os.makedirs(logdir)
  1057. """
  1058. pass
  1059. def __enter__(self):
  1060. self.setup()
  1061. return self
  1062. def __exit__(self, exc_type, exc_value, exc_traceback):
  1063. # only close the app if there are no unhandled exceptions
  1064. if exc_type is None:
  1065. self.close()