Package: class-factory
class_factory - package for factory mechanism for sub classes and auto import mechanismThis package contains the 'ClassFactoryUser' class that allow a parent class and its subclass to access
a factory mechanism to create new instances.The class also enable the automatic import mehanism for subclasses. Handy when you design a all
familly of sub classes or plugin mechanism.Basic UseWhat you have to do is listed in the doc of the base class :"""This class represents the base class for classes using a factory for their subclassesThe following methods have to be overloaded :* __get_factory_id* __get_import_data_for_factory* __get_base_class_for_factory* __get_id_function_name_for_factoryThe following methods may be overloaded :* __get_handled_exceptions_for_factory* __get_id_exception_for_factory* add_base_class"""You also have of course to inherit from this base class :fromclass_factoryimportClassFactoryUserclassMyParentClass(ClassFactoryUser):...Description of the methods to overload__get_factory_iddoc@classmethoddef__get_factory_id(cls):"""This method is designed to get the ID of the factory dedicated to this classThe name of the directory where subclasses are to be found is a good idea:rtype: str:return: the ID of the factory dedicated to this class"""DetailsIndeed, this method just requires to return a simple str ID. If you have absolutely no idea,
you can event return an empty string.The role of this string is to differentiate several families of subclass, so if you have a single
family don't worry about it.Example of completion@classmethoddef__get_factory_id(cls):"""This method is designed to get the ID of the factory dedicated to this classThe name of the directory where subclasses are to be found is a good idea:rtype: str:return: the ID of the factory dedicated to this class"""return"My Plugins"__get_base_class_for_factorydoc@classmethoddef__get_base_class_for_factory(cls):"""This method is designed to get the base class for the factory:rtype: type:return: the base class for the factory"""DetailsHere you have to return a class the one from which all subclasses referenced in the factory will inherit.Usualy, you will return the class that directly inherit from ClassFactoryUser. Be careful in this
case and return precisely the class not 'cls' as it would change for any subclass.Example of completion@classmethoddef__get_base_class_for_factory(cls):"""This method is designed to get the base class for the factory:rtype: type:return: the base class for the factory"""returnMyParentClass__get_id_function_name_for_factorydoc@classmethoddef__get_id_function_name_for_factory(cls):"""This method is designed to get the name of the method returning the ID of the class for the factory:rtype: str:return: the name of the method returning the ID of the class for the factory"""DetailsIn the factory, each sub class is identified by an ID. This ID is taken as the result of a class
method. You will have to implement such a class method, but the factory requires the name of this method.
This is precisely what you have to return here.Example of completion@classmethoddef__get_id_function_name_for_factory(cls):"""This method is designed to get the name of the method returning the ID of the class for the factory:rtype: str:return: the name of the method returning the ID of the class for the factory"""return"get_plugin_id"__get_import_data_for_factorydoc@classmethoddef__get_import_data_for_factory(cls):"""This method is designed to get the list of settings to import subclasses. Each item of the list contains :- import_path : path from which files to import are searched recursively : <import_path>/*/**/<format_name>- import_val : string replacing import path to import the files.ex : <import_path>/a/b/<file name>- if import_val == "":the file will be imported this way : "from a.b import <module name>- otherwise:the file will be imported this way  "from <import_val>.a.b import <module name>- format_name : glob token used to find the file to import (ex : task_*.py):rtype: list[(str, str, str)]:return: the list of settings to import subclasses : (import_path, import_val, format_name)"""DetailsThis is the technical part. You will have to tell the factory from where and how it will import the subclasses.
You may want to import from different directories. This is usually the case when you have embeded plugins
and user designed plugins in an other directory.
All python modules containing a sub class will be searched through a glob pattern. To ease this mechanism
it is a good practice to have a precise prefix or suffix for all those modules.For each directory, you will have to return as described in the doc :import_path : path from which files to import are searched recursively :import_val : string replacing import path to import the files.format_name : glob token used to find the file to import (ex : task_*.py)Example of completion@classmethoddef__get_import_data_for_factory(cls):"""This method is designed to get the list of settings to import subclasses. Each item of the list contains :- import_path : path from which files to import are searched recursively : <import_path>/*/**/<format_name>- import_val : string replacing import path to import the files.ex : <import_path>/a/b/<file name>- if import_val == "":the file will be imported this way : "from a.b import <module name>- otherwise:the file will be imported this way  "from <import_val>.a.b import <module name>- format_name : glob token used to find the file to import (ex : task_*.py):rtype: list[(str, str, str)]:return: the list of settings to import subclasses : (import_path, import_val, format_name)"""my_dir=os.path.dirname(os.path.abspath("__file__"))import_path=os.path.join(my_dir,"plugins")format_name="*_plugin.py"import_val="my_appp.plugins"return[(import_path,import_val,format_name)]add_base_classdoc@classmethoddefadd_base_class(cls):"""This method is designed to know if the base class must be regitred in the factory:rtype: bool:return: True if the base class must be registred in the factory, False otherwise"""DetailsUsualy, the base class is not contained by the factory (supposed to be an abstract class). If you
want to register the base class, have this method return True. (Default behaviour is 'return False')Example of completion@classmethoddefadd_base_class(cls):"""This method is designed to know if the base class must be regitred in the factory:rtype: bool:return: True if the base class must be registred in the factory, False otherwise"""returnTrue__get_handled_exceptions_for_factorydoc@classmethoddef__get_handled_exceptions_for_factory(cls):"""This method is designed to get the tuple of exception types to be displayed directly,without traceback when there is an import problem:rtype handled_exceptions: tuple[Type[Exception]]:return: tuple of exception types to be displayed directly, without traceback when there is an import problem"""DetailsThe factory mechanism as been designed in order to ignore bugs when a sub class module is imported.
A warning is still displayed. The default behaviour is to display the full traceback of the error that
occured. However sometime, you may have conceived a precise error/exception and simply displaying this
exception is enough. For instance, when one of your pluggin depends on a third party library.Here you have to return the list of Exceptions that will be directly displayed without tracebackExample of completion@classmethoddef__get_handled_exceptions_for_factory(cls):"""This method is designed to get the tuple of exception types to be displayed directly,without traceback when there is an import problem:rtype handled_exceptions: tuple[Type[Exception]]:return: tuple of exception types to be displayed directly, without traceback when there is an import problem"""return(PyQtNotFoundLibrary,IncompleteEnvironmentError)__get_id_exception_for_factorydoc@classmethoddef__get_id_exception_for_factory(cls):"""This method is designed to get the Exception or method instanciating an Exception when an ID is not found:rtype: NoneType | (str) -> Exception:return: Exception or method instanciating an Exception when an ID is not found"""DetailsWhen you will use the factory, you will have to specify the ID of the instance you want to create.
Having a dedicated error for a unfound ID may be usefull for you, and you may have designed such an Exception.This method must return this exception, or a function that can return such an exception from the missing ID.Example of completion@classmethoddef__get_id_exception_for_factory(cls):"""This method is designed to get the Exception or method instanciating an Exception when an ID is not found:rtype: NoneType | (str) -> Exception:return: Exception or method instanciating an Exception when an ID is not found"""returnPluginIdNotFoundExceptionExample of fully completed parent classimportosfromclass_factoryimportClassFactoryUser# ====================================classMyParentClass(ClassFactoryUser):"""This class is parent class for all plugins"""# ==========@classmethoddef__get_factory_id(cls):"""This method is designed to get the ID of the factory dedicated to this classThe name of the directory where subclasses are to be found is a good idea:rtype: str:return: the ID of the factory dedicated to this class"""return"My Plugins"# ==========@classmethoddef__get_base_class_for_factory(cls):"""This method is designed to get the base class for the factory:rtype: type:return: the base class for the factory"""returnMyParentClass# ==========@classmethoddef__get_id_function_name_for_factory(cls):"""This method is designed to get the name of the method returning the ID of the class for the factory:rtype: str:return: the name of the method returning the ID of the class for the factory"""return"get_plugin_id"# ==========@classmethoddef__get_import_data_for_factory(cls):"""This method is designed to get the list of settings to import subclasses. Each item of the list contains :- import_path : path from which files to import are searched recursively : <import_path>/*/**/<format_name>- import_val : string replacing import path to import the files.ex : <import_path>/a/b/<file name>- if import_val == "":the file will be imported this way : "from a.b import <module name>- otherwise:the file will be imported this way  "from <import_val>.a.b import <module name>- format_name : glob token used to find the file to import (ex : task_*.py):rtype: list[(str, str, str)]:return: the list of settings to import subclasses : (import_path, import_val, format_name)"""my_dir=os.path.dirname(os.path.abspath("__file__"))import_path=os.path.join(my_dir,"plugins")format_name="*_plugin.py"import_val="my_appp.plugins"return[(import_path,import_val,format_name)]# ==========@classmethoddefadd_base_class(cls):"""This method is designed to know if the base class must be regitred in the factory:rtype: bool:return: True if the base class must be registred in the factory, False otherwise"""returnTrue# ==========@classmethoddef__get_handled_exceptions_for_factory(cls):"""This method is designed to get the tuple of exception types to be displayed directly,without traceback when there is an import problem:rtype handled_exceptions: tuple[Type[Exception]]:return: tuple of exception types to be displayed directly, without traceback when there is an import problem"""return(PyQtNotFoundLibrary,IncompleteEnvironmentError)# ==========@classmethoddef__get_id_exception_for_factory(cls):"""This method is designed to get the Exception or method instanciating an Exception when an ID is not found:rtype: NoneType | (str) -> Exception:return: Exception or method instanciating an Exception when an ID is not found"""returnPluginIdNotFoundException# ==========@classmethoddefget_plugin_id(cls):"""This method is designed to get the ID of the plugin:rtype: str:return: the ID of the plugin"""returncls.__name__Designing a sub classExampleclassMyPlugin(MyParentClass):"""Class for a plugin"""# ==========@classmethoddefget_plugin_id(cls):"""This method is designed to get the ID of the plugin:rtype: str:return: the ID of the plugin"""return"MY"DetailsRemember the '__get_id_function_name_for_factory' method returning "get_plugin_id" ?
It means that your sub class (in this example) must implement a 'get_plugin_id' class method.In the previous section the method returned 'cls._name_'. This is a convenient way to have a default
behavious to generate all the ideas, but you need to use a specific ID for each sub class.Simply overload the method whose name is returned from '__get_id_function_name_for_factory' and don't
forget to do it ! It is the only thing you have to do in your sub classes.Using the factoryAccess the factoryOnce all your sub classes have been created and registered in the factory, you'll want to use them.First, you have to get the factory. You can access it from any sub class of ClassFactoryUserfactory=MyParentClass.get_factory()Then the factory grants you access to several methodsMethods of the factoryget_ids_iteratorThe get_ids_iterator method returns an iterator over the different IDs of classes handled by the factory.get_idsSimilar to the previous method but returns a list instead of an iterator.get_class_from_idThis method returns a registered class from its ID.get_instance_from_idThis method is precisely what you expect from a factory and returns an instance of a registered class.The first argument of the method is the ID of the class from which you want an instane.The other arguments are the arguments you pass to the class to build your instance