handler.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. """
  2. Cement core handler module.
  3. """
  4. import re
  5. from ..core import exc, meta
  6. from ..core import backend
  7. from ..utils.misc import minimal_logger
  8. LOG = minimal_logger(__name__)
  9. class HandlerManager(object):
  10. """
  11. Manages the handler system to define, get, resolve, etc handlers with
  12. the Cement Framework.
  13. :param use_backend_globals: Whether to use backend globals (backward
  14. compatibility and deprecated).
  15. """
  16. def __init__(self, use_backend_globals=False):
  17. if use_backend_globals is True:
  18. self.__handlers__ = backend.__handlers__
  19. else:
  20. self.__handlers__ = {}
  21. def get(self, handler_type, handler_label, *args):
  22. """
  23. Get a handler object.
  24. :param handler_type: The type of handler (i.e. ``output``)
  25. :type handler_type: ``str``
  26. :param handler_label: The label of the handler (i.e. ``json``)
  27. :type handler_label: ``str``
  28. :param fallback: A fallback value to return if handler_label doesn't
  29. exist.
  30. :returns: An uninstantiated handler object
  31. :raises: :class:`cement.core.exc.FrameworkError`
  32. Usage:
  33. .. code-block:: python
  34. output = app.handler.get('output', 'json')
  35. output.render(dict(foo='bar'))
  36. """
  37. if handler_type not in self.__handlers__:
  38. raise exc.FrameworkError("handler type '%s' does not exist!" %
  39. handler_type)
  40. if handler_label in self.__handlers__[handler_type]:
  41. return self.__handlers__[handler_type][handler_label]
  42. elif len(args) > 0:
  43. return args[0]
  44. else:
  45. raise exc.FrameworkError("handlers['%s']['%s'] does not exist!" %
  46. (handler_type, handler_label))
  47. def list(self, handler_type):
  48. """
  49. Return a list of handlers for a given ``handler_type``.
  50. :param handler_type: The type of handler (i.e. ``output``)
  51. :returns: List of handlers that match ``hander_type``.
  52. :rtype: ``list``
  53. :raises: :class:`cement.core.exc.FrameworkError`
  54. Usage:
  55. .. code-block:: python
  56. app.handler.list('log')
  57. """
  58. if handler_type not in self.__handlers__:
  59. raise exc.FrameworkError("handler type '%s' does not exist!" %
  60. handler_type)
  61. res = []
  62. for label in self.__handlers__[handler_type]:
  63. if label == '__interface__':
  64. continue
  65. res.append(self.__handlers__[handler_type][label])
  66. return res
  67. def define(self, interface):
  68. """
  69. Define a handler based on the provided interface. Defines a handler
  70. type based on ``<interface>.IMeta.label``.
  71. :param interface: The interface class that defines the interface to be
  72. implemented by handlers.
  73. :raises: :class:`cement.core.exc.InterfaceError`
  74. :raises: :class:`cement.core.exc.FrameworkError`
  75. Usage:
  76. .. code-block:: python
  77. app.handler.define(IDatabaseHandler)
  78. """
  79. if not hasattr(interface, 'IMeta'):
  80. raise exc.InterfaceError("Invalid %s, " % interface +
  81. "missing 'IMeta' class.")
  82. if not hasattr(interface.IMeta, 'label'):
  83. raise exc.InterfaceError("Invalid %s, " % interface +
  84. "missing 'IMeta.label' class.")
  85. LOG.debug("defining handler type '%s' (%s)" %
  86. (interface.IMeta.label, interface.__name__))
  87. if interface.IMeta.label in self.__handlers__:
  88. raise exc.FrameworkError("Handler type '%s' already defined!" %
  89. interface.IMeta.label)
  90. self.__handlers__[interface.IMeta.label] = {
  91. '__interface__': interface
  92. }
  93. def defined(self, handler_type):
  94. """
  95. Test whether ``handler_type`` is defined.
  96. :param handler_type: The name or ``handler_type`` of the handler (I.e.
  97. ``log``, ``config``, ``output``, etc).
  98. :returns: True if the handler type is defined, False otherwise.
  99. :rtype: ``boolean``
  100. Usage:
  101. .. code-block:: python
  102. app.handler.defined('log')
  103. """
  104. if handler_type in self.__handlers__:
  105. return True
  106. else:
  107. return False
  108. def register(self, handler_obj):
  109. """
  110. Register a handler object to a handler. If the same object is already
  111. registered then no exception is raised, however if a different object
  112. attempts to be registered to the same name a ``FrameworkError`` is
  113. raised.
  114. :param handler_obj: The uninstantiated handler object to register.
  115. :raises: :class:`cement.core.exc.InterfaceError`
  116. :raises: :class:`cement.core.exc.FrameworkError`
  117. Usage:
  118. .. code-block:: python
  119. class MyDatabaseHandler(object):
  120. class Meta:
  121. interface = IDatabase
  122. label = 'mysql'
  123. def connect(self):
  124. # ...
  125. app.handler.register(MyDatabaseHandler)
  126. """
  127. orig_obj = handler_obj
  128. # for checks
  129. obj = orig_obj()
  130. if not hasattr(obj._meta, 'label') or not obj._meta.label:
  131. raise exc.InterfaceError("Invalid handler %s, " % orig_obj +
  132. "missing '_meta.label'.")
  133. if not hasattr(obj._meta, 'interface') or not obj._meta.interface:
  134. raise exc.InterfaceError("Invalid handler %s, " % orig_obj +
  135. "missing '_meta.interface'.")
  136. # translate dashes to underscores
  137. orig_obj.Meta.label = re.sub('-', '_', obj._meta.label)
  138. obj._meta.label = re.sub('-', '_', obj._meta.label)
  139. handler_type = obj._meta.interface.IMeta.label
  140. LOG.debug("registering handler '%s' into handlers['%s']['%s']" %
  141. (orig_obj, handler_type, obj._meta.label))
  142. if handler_type not in self.__handlers__:
  143. raise exc.FrameworkError("Handler type '%s' doesn't exist." %
  144. handler_type)
  145. if obj._meta.label in self.__handlers__[handler_type] and \
  146. self.__handlers__[handler_type][obj._meta.label] != obj:
  147. raise exc.FrameworkError("handlers['%s']['%s'] already exists" %
  148. (handler_type, obj._meta.label))
  149. interface = self.__handlers__[handler_type]['__interface__']
  150. if hasattr(interface.IMeta, 'validator'):
  151. interface.IMeta().validator(obj)
  152. else:
  153. LOG.debug("Interface '%s' does not have a validator() function!" %
  154. interface)
  155. self.__handlers__[handler_type][obj.Meta.label] = orig_obj
  156. def registered(self, handler_type, handler_label):
  157. """
  158. Check if a handler is registered.
  159. :param handler_type: The type of handler (interface label)
  160. :param handler_label: The label of the handler
  161. :returns: True if the handler is registered, False otherwise
  162. :rtype: ``boolean``
  163. Usage:
  164. .. code-block:: python
  165. app.handler.registered('log', 'colorlog')
  166. """
  167. if handler_type in self.__handlers__ and \
  168. handler_label in self.__handlers__[handler_type]:
  169. return True
  170. return False
  171. def resolve(self, handler_type, handler_def, raise_error=True):
  172. """
  173. Resolves the actual handler, as it can be either a string identifying
  174. the handler to load from self.__handlers__, or it can be an
  175. instantiated or non-instantiated handler class.
  176. :param handler_type: The type of handler (aka the interface label)
  177. :param hander_def: The handler as defined in CementApp.Meta.
  178. :type handler_def: str, uninstantiated object, or instantiated object
  179. :param raise_error: Whether or not to raise an exception if unable
  180. to resolve the handler.
  181. :type raise_error: boolean
  182. :returns: The instantiated handler object.
  183. Usage:
  184. .. code-block:: python
  185. # via label (str)
  186. log = app.handler.resolve('log', 'colorlog')
  187. # via uninstantiated handler class
  188. log = app.handler.resolve('log', ColorLogHanddler)
  189. # via instantiated handler instance
  190. log = app.handler.resolve('log', ColorLogHandler())
  191. """
  192. han = None
  193. if type(handler_def) == str:
  194. han = self.get(handler_type, handler_def)()
  195. elif hasattr(handler_def, '_meta'):
  196. if not self.registered(handler_type, handler_def._meta.label):
  197. self.register(handler_def.__class__)
  198. han = handler_def
  199. elif hasattr(handler_def, 'Meta'):
  200. han = handler_def()
  201. if not self.registered(handler_type, han._meta.label):
  202. self.register(handler_def)
  203. msg = "Unable to resolve handler '%s' of type '%s'" % \
  204. (handler_def, handler_type)
  205. if han is not None:
  206. return han
  207. elif han is None and raise_error:
  208. raise exc.FrameworkError(msg)
  209. elif han is None:
  210. LOG.debug(msg)
  211. return None
  212. class CementBaseHandler(meta.MetaMixin):
  213. """Base handler class that all Cement Handlers should subclass from."""
  214. class Meta:
  215. """
  216. Handler meta-data (can also be passed as keyword arguments to the
  217. parent class).
  218. """
  219. label = None
  220. """The string identifier of this handler."""
  221. interface = None
  222. """The interface that this class implements."""
  223. config_section = None
  224. """
  225. A config [section] to merge config_defaults with.
  226. Note: Though Meta.config_section defaults to None, Cement will
  227. set this to the value of ``<interface_label>.<handler_label>`` if
  228. no section is set by the user/developer.
  229. """
  230. config_defaults = None
  231. """
  232. A config dictionary that is merged into the applications config
  233. in the ``[<config_section>]`` block. These are defaults and do not
  234. override any existing defaults under that section.
  235. """
  236. overridable = False
  237. """
  238. Whether or not handler can be overridden by
  239. ``CementApp.Meta.handler_override_options``. Will be listed as an
  240. available choice to override the specific handler (i.e.
  241. ``CementApp.Meta.output_handler``, etc).
  242. """
  243. def __init__(self, **kw):
  244. super(CementBaseHandler, self).__init__(**kw)
  245. self.app = None
  246. def _setup(self, app_obj):
  247. """
  248. The _setup function is called during application initialization and
  249. must ``setup`` the handler object making it ready for the framework
  250. or the application to make further calls to it.
  251. :param app_obj: The application object.
  252. :returns: None
  253. """
  254. self.app = app_obj
  255. if self._meta.config_section is None:
  256. self._meta.config_section = "%s.%s" % \
  257. (self._meta.interface.IMeta.label, self._meta.label)
  258. if self._meta.config_defaults is not None:
  259. LOG.debug("merging config defaults from '%s' " % self +
  260. "into section '%s'" % self._meta.config_section)
  261. dict_obj = dict()
  262. dict_obj[self._meta.config_section] = self._meta.config_defaults
  263. self.app.config.merge(dict_obj, override=False)
  264. def get(handler_type, handler_label, *args):
  265. """
  266. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  267. will be removed in future versions of Cement.
  268. Use ``CementApp.handler.get()`` instead.
  269. ---
  270. Get a handler object.
  271. Required Arguments:
  272. :param handler_type: The type of handler (i.e. 'output')
  273. :type handler_type: str
  274. :param handler_label: The label of the handler (i.e. 'json')
  275. :type handler_label: str
  276. :param fallback: A fallback value to return if handler_label doesn't
  277. exist.
  278. :returns: An uninstantiated handler object
  279. :raises: cement.core.exc.FrameworkError
  280. Usage:
  281. from cement.core import handler
  282. output = handler.get('output', 'json')
  283. output.render(dict(foo='bar'))
  284. """
  285. # only log debug for now as this won't be removed until Cement 3.x and
  286. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  287. LOG.debug(
  288. 'Cement Deprecation Warning: `handler.get()` has been deprecated, '
  289. 'and will be removed in future versions of Cement. You should now '
  290. 'use `CementApp.handler.get()` instead.'
  291. )
  292. if handler_type not in backend.__handlers__:
  293. raise exc.FrameworkError("handler type '%s' does not exist!" %
  294. handler_type)
  295. if handler_label in backend.__handlers__[handler_type]:
  296. return backend.__handlers__[handler_type][handler_label]
  297. elif len(args) > 0:
  298. return args[0]
  299. else:
  300. raise exc.FrameworkError("handlers['%s']['%s'] does not exist!" %
  301. (handler_type, handler_label))
  302. def list(handler_type):
  303. """
  304. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  305. will be removed in future versions of Cement.
  306. Use ``CementApp.handler.list()`` instead.
  307. ---
  308. Return a list of handlers for a given type.
  309. :param handler_type: The type of handler (i.e. 'output')
  310. :returns: List of handlers that match `type`.
  311. :rtype: ``list``
  312. :raises: cement.core.exc.FrameworkError
  313. """
  314. # only log debug for now as this won't be removed until Cement 3.x and
  315. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  316. LOG.debug(
  317. 'Cement Deprecation Warning: `handler.get()` has been deprecated, '
  318. 'and will be removed in future versions of Cement. You should now '
  319. 'use `CementApp.handler.get()` instead.'
  320. )
  321. if handler_type not in backend.__handlers__:
  322. raise exc.FrameworkError("handler type '%s' does not exist!" %
  323. handler_type)
  324. res = []
  325. for label in backend.__handlers__[handler_type]:
  326. if label == '__interface__':
  327. continue
  328. res.append(backend.__handlers__[handler_type][label])
  329. return res
  330. def define(interface):
  331. """
  332. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  333. will be removed in future versions of Cement.
  334. Use ``CementApp.handler.define()`` instead.
  335. ---
  336. Define a handler based on the provided interface. Defines a handler type
  337. based on <interface>.IMeta.label.
  338. :param interface: The interface class that defines the interface to be
  339. implemented by handlers.
  340. :raises: cement.core.exc.InterfaceError
  341. :raises: cement.core.exc.FrameworkError
  342. Usage:
  343. .. code-block:: python
  344. from cement.core import handler
  345. handler.define(IDatabaseHandler)
  346. """
  347. # only log debug for now as this won't be removed until Cement 3.x and
  348. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  349. LOG.debug(
  350. 'Cement Deprecation Warning: `handler.define()` has been deprecated, '
  351. 'and will be removed in future versions of Cement. You should now '
  352. 'use `CementApp.handler.define()` instead.'
  353. )
  354. if not hasattr(interface, 'IMeta'):
  355. raise exc.InterfaceError("Invalid %s, " % interface +
  356. "missing 'IMeta' class.")
  357. if not hasattr(interface.IMeta, 'label'):
  358. raise exc.InterfaceError("Invalid %s, " % interface +
  359. "missing 'IMeta.label' class.")
  360. LOG.debug("defining handler type '%s' (%s)" %
  361. (interface.IMeta.label, interface.__name__))
  362. if interface.IMeta.label in backend.__handlers__:
  363. raise exc.FrameworkError("Handler type '%s' already defined!" %
  364. interface.IMeta.label)
  365. backend.__handlers__[interface.IMeta.label] = {'__interface__': interface}
  366. def defined(handler_type):
  367. """
  368. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  369. will be removed in future versions of Cement.
  370. Use ``CementApp.handler.defined()`` instead.
  371. ---
  372. Test whether a handler type is defined.
  373. :param handler_type: The name or 'type' of the handler (I.e. 'logging').
  374. :returns: True if the handler type is defined, False otherwise.
  375. :rtype: ``boolean``
  376. """
  377. # only log debug for now as this won't be removed until Cement 3.x and
  378. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  379. LOG.debug(
  380. 'Cement Deprecation Warning: `handler.defined()` has been deprecated, '
  381. 'and will be removed in future versions of Cement. You should now '
  382. 'use `CementApp.handler.defined()` instead.'
  383. )
  384. if handler_type in backend.__handlers__:
  385. return True
  386. else:
  387. return False
  388. def register(handler_obj):
  389. """
  390. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  391. will be removed in future versions of Cement.
  392. Use ``CementApp.handler.register()`` instead.
  393. ---
  394. Register a handler object to a handler. If the same object is already
  395. registered then no exception is raised, however if a different object
  396. attempts to be registered to the same name a FrameworkError is
  397. raised.
  398. :param handler_obj: The uninstantiated handler object to register.
  399. :raises: cement.core.exc.InterfaceError
  400. :raises: cement.core.exc.FrameworkError
  401. Usage:
  402. .. code-block:: python
  403. from cement.core import handler
  404. class MyDatabaseHandler(object):
  405. class Meta:
  406. interface = IDatabase
  407. label = 'mysql'
  408. def connect(self):
  409. ...
  410. handler.register(MyDatabaseHandler)
  411. """
  412. # only log debug for now as this won't be removed until Cement 3.x and
  413. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  414. LOG.debug(
  415. 'Cement Deprecation Warning: `handler.register()` has been '
  416. 'deprecated, and will be removed in future versions of Cement. You '
  417. 'should now use `CementApp.handler.register()` instead.'
  418. )
  419. orig_obj = handler_obj
  420. # for checks
  421. obj = orig_obj()
  422. if not hasattr(obj._meta, 'label') or not obj._meta.label:
  423. raise exc.InterfaceError("Invalid handler %s, " % orig_obj +
  424. "missing '_meta.label'.")
  425. if not hasattr(obj._meta, 'interface') or not obj._meta.interface:
  426. raise exc.InterfaceError("Invalid handler %s, " % orig_obj +
  427. "missing '_meta.interface'.")
  428. # translate dashes to underscores
  429. orig_obj.Meta.label = re.sub('-', '_', obj._meta.label)
  430. obj._meta.label = re.sub('-', '_', obj._meta.label)
  431. handler_type = obj._meta.interface.IMeta.label
  432. LOG.debug("registering handler '%s' into handlers['%s']['%s']" %
  433. (orig_obj, handler_type, obj._meta.label))
  434. if handler_type not in backend.__handlers__:
  435. raise exc.FrameworkError("Handler type '%s' doesn't exist." %
  436. handler_type)
  437. if obj._meta.label in backend.__handlers__[handler_type] and \
  438. backend.__handlers__[handler_type][obj._meta.label] != obj:
  439. raise exc.FrameworkError("handlers['%s']['%s'] already exists" %
  440. (handler_type, obj._meta.label))
  441. interface = backend.__handlers__[handler_type]['__interface__']
  442. if hasattr(interface.IMeta, 'validator'):
  443. interface.IMeta().validator(obj)
  444. else:
  445. LOG.debug("Interface '%s' does not have a validator() function!" %
  446. interface)
  447. backend.__handlers__[handler_type][obj.Meta.label] = orig_obj
  448. def registered(handler_type, handler_label):
  449. """
  450. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  451. will be removed in future versions of Cement.
  452. Use ``CementApp.handler.registered()`` instead.
  453. ---
  454. Check if a handler is registered.
  455. :param handler_type: The type of handler (interface label)
  456. :param handler_label: The label of the handler
  457. :returns: True if the handler is registered, False otherwise
  458. :rtype: ``boolean``
  459. """
  460. # only log debug for now as this won't be removed until Cement 3.x and
  461. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  462. LOG.debug(
  463. 'Cement Deprecation Warning: `handler.registered()` has been '
  464. 'deprecated, and will be removed in future versions of Cement. You '
  465. 'should now use `CementApp.handler.registered()` instead.'
  466. )
  467. if handler_type in backend.__handlers__ and \
  468. handler_label in backend.__handlers__[handler_type]:
  469. return True
  470. return False
  471. def resolve(handler_type, handler_def, raise_error=True):
  472. """
  473. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  474. will be removed in future versions of Cement.
  475. Use ``CementApp.handler.resolve()`` instead.
  476. ---
  477. Resolves the actual handler, as it can be either a string identifying
  478. the handler to load from backend.__handlers__, or it can be an
  479. instantiated or non-instantiated handler class.
  480. :param handler_type: The type of handler (aka the interface label)
  481. :param hander_def: The handler as defined in CementApp.Meta.
  482. :type handler_def: str, uninstantiated object, or instantiated object
  483. :param raise_error: Whether or not to raise an exception if unable
  484. to resolve the handler.
  485. :type raise_error: boolean
  486. :returns: The instantiated handler object.
  487. """
  488. # only log debug for now as this won't be removed until Cement 3.x and
  489. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  490. LOG.debug(
  491. 'Cement Deprecation Warning: `handler.resove()` has been '
  492. 'deprecated, and will be removed in future versions of Cement. You '
  493. 'should now use `CementApp.handler.resolve()` instead.'
  494. )
  495. han = None
  496. if type(handler_def) == str:
  497. han = get(handler_type, handler_def)()
  498. elif hasattr(handler_def, '_meta'):
  499. if not registered(handler_type, handler_def._meta.label):
  500. register(handler_def.__class__)
  501. han = handler_def
  502. elif hasattr(handler_def, 'Meta'):
  503. han = handler_def()
  504. if not registered(handler_type, han._meta.label):
  505. register(handler_def)
  506. msg = "Unable to resolve handler '%s' of type '%s'" % \
  507. (handler_def, handler_type)
  508. if han is not None:
  509. return han
  510. elif han is None and raise_error:
  511. raise exc.FrameworkError(msg)
  512. elif han is None:
  513. LOG.debug(msg)
  514. return None