'''
Factory object
==============
The factory can be used to automatically register any class or module
and instantiate classes from it anywhere in your project. It is an
implementation of the
`Factory Pattern <http://en.wikipedia.org/wiki/Factory_pattern>`_.
The class list and available modules are automatically generated by setup.py.
Example for registering a class/module::
>>> from kivy.factory import Factory
>>> Factory.register('Widget', module='kivy.uix.widget')
>>> Factory.register('Vector', module='kivy.vector')
Example of using the Factory::
>>> from kivy.factory import Factory
>>> widget = Factory.Widget(pos=(456,456))
>>> vector = Factory.Vector(9, 2)
Example using a class name::
>>> from kivy.factory import Factory
>>> Factory.register('MyWidget', cls=MyWidget)
By default, the first classname you register via the factory is permanent.
If you wish to change the registered class, you need to unregister the
classname before you re-assign it::
>>> from kivy.factory import Factory
>>> Factory.register('MyWidget', cls=MyWidget)
>>> widget = Factory.MyWidget()
>>> Factory.unregister('MyWidget')
>>> Factory.register('MyWidget', cls=CustomWidget)
>>> customWidget = Factory.MyWidget()
'''
__all__ = ('Factory', 'FactoryBase', 'FactoryException')
import copy
from kivy.logger import Logger
from kivy.context import register_context
[docs]class FactoryException(Exception):
pass
class FactoryBase(object):
def __init__(self):
super(FactoryBase, self).__init__()
self.classes = {}
@classmethod
def create_from(cls, factory):
"""Creates a instance of the class, and initializes to the state of
``factory``.
:param factory: The factory to initialize from.
:return: A new instance of this class.
"""
obj = cls()
obj.classes = copy.copy(factory.classes)
return obj
def is_template(self, classname):
'''Return True if the classname is a template from the
:class:`~kivy.lang.Builder`.
.. versionadded:: 1.0.5
'''
if classname in self.classes:
return self.classes[classname]['is_template']
else:
return False
def register(self, classname, cls=None, module=None, is_template=False,
baseclasses=None, filename=None, warn=False):
'''Register a new classname referring to a real class or
class definition in a module. Warn, if True will emit a warning message
when a class is re-declared.
.. versionchanged:: 1.9.0
`warn` was added.
.. versionchanged:: 1.7.0
:attr:`baseclasses` and :attr:`filename` added
.. versionchanged:: 1.0.5
:attr:`is_template` has been added in 1.0.5.
'''
if cls is None and module is None and baseclasses is None:
raise ValueError(
'You must specify either cls= or module= or baseclasses =')
if classname in self.classes:
if warn:
info = self.classes[classname]
Logger.warning('Factory: Ignored class "{}" re-declaration. '
'Current - module: {}, cls: {}, baseclass: {}, filename: {}. '
'Ignored - module: {}, cls: {}, baseclass: {}, filename: {}.'.
format(classname, info['module'], info['cls'],
info['baseclasses'], info['filename'], module, cls,
baseclasses, filename))
return
self.classes[classname] = {
'module': module,
'cls': cls,
'is_template': is_template,
'baseclasses': baseclasses,
'filename': filename}
def unregister(self, *classnames):
'''Unregisters the classnames previously registered via the
register method. This allows the same classnames to be re-used in
different contexts.
.. versionadded:: 1.7.1
'''
for classname in classnames:
if classname in self.classes:
self.classes.pop(classname)
def unregister_from_filename(self, filename):
'''Unregister all the factory objects related to the filename passed in
the parameter.
.. versionadded:: 1.7.0
'''
to_remove = [x for x in self.classes
if self.classes[x]['filename'] == filename]
for name in to_remove:
del self.classes[name]
def __getattr__(self, name):
classes = self.classes
if name not in classes:
if name[0] == name[0].lower():
# if trying to access attributes like checking for `bind`
# then raise AttributeError
raise AttributeError(
'First letter of class name <%s> is in lowercase' % name)
raise FactoryException('Unknown class <%s>' % name)
item = classes[name]
cls = item['cls']
# No class to return, import the module
if cls is None:
if item['module']:
module = __import__(
name=item['module'],
fromlist='*',
level=0 # force absolute
)
if not hasattr(module, name):
raise FactoryException(
'No class named <%s> in module <%s>' % (
name, item['module']))
cls = item['cls'] = getattr(module, name)
elif item['baseclasses']:
rootwidgets = []
for basecls in item['baseclasses'].split('+'):
rootwidgets.append(Factory.get(basecls))
cls = item['cls'] = type(str(name), tuple(rootwidgets), {})
else:
raise FactoryException('No information to create the class')
return cls
get = __getattr__
#: Factory instance to use for getting new classes
Factory: FactoryBase = register_context('Factory', FactoryBase)
# Now import the file with all registers
# automatically generated by build_factory
import kivy.factory_registers # NOQA
Logger.info('Factory: %d symbols loaded' % len(Factory.classes))
if __name__ == '__main__':
Factory.register('Vector', module='kivy.vector')
Factory.register('Widget', module='kivy.uix.widget')