{"id":463,"date":"2016-03-27T18:10:44","date_gmt":"2016-03-27T10:10:44","guid":{"rendered":"https:\/\/www.techcoil.com\/blog\/?p=463"},"modified":"2018-09-04T22:58:51","modified_gmt":"2018-09-04T14:58:51","slug":"how-to-dynamically-look-for-classes-defined-in-python-3-file","status":"publish","type":"post","link":"https:\/\/www.techcoil.com\/blog\/how-to-dynamically-look-for-classes-defined-in-python-3-file\/","title":{"rendered":"How to look for classes defined in Python 3 files dynamically"},"content":{"rendered":"<p>There are times when we need to write Python codes that extends the functionality of others, especially when we are writing a framework that allows for custom extension. To allow my Python program to be extended by others via the template design pattern, the first exploration task that I did was to find out how to dynamically look for classes defined in arbitrary Python files. <\/p>\n<p>This post documents a way to look for classes defined in Python 3 files dynamically.<\/p>\n<h3>Creating a sample scenario<\/h3>\n<p>Suppose we are going to build a mechanism that allows custom Python codes to be notified of various events that our core Python 3 program generates while it is running. For the sake of simplicity, let's limit the scope of this exploration task to allowing custom codes to be notified after our core Python 3 program starts running.<\/p>\n<h4>Predefining the file structure of our Python program<\/h4>\n<p>In order not to exhaust our program too badly, we designate a folder to contain the custom codes. We name this folder as <code>plugins<\/code>. Codes that belong together should be contained in their own folder, for example, <code>plugins\/hello-world<\/code> should contain the custom codes for us to output \"Hello World!\" when our program starts up.<\/p>\n<p>The entry point which the plugin will be picked up by the core program should be done via a class that extends the <code>Plugin<\/code> class which is defined in the <code>lib<\/code> folder. To facilitate the management of plugins, let's have a <code>PluginManager<\/code> class which will help look up of classes that contain the custom codes so that the custom codes can be notified when events are generated from our core Python program. <\/p>\n<p>With these rules in mind, we will have the following file structure:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nprogram-root\r\n  lib\r\n    plugin.py\r\n    plugin_manager.py\r\n  plugin\r\n    hello-world\r\n      hello_world_plugin.py\r\n  app.py\r\n<\/pre>\n<h3>Defining the <code>Plugin<\/code> class<\/h3>\n<p>We first define the <code>Plugin<\/code> class which our custom codes will extend so that they can be notified when our core program generates some events:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport abc\r\n\r\nclass Plugin: \r\n    \r\n    @abc.abstractmethod\r\n    def event_triggered(self, event_name='', resource=''): \r\n        return\r\n\r\n<\/pre>\n<p>In the <code>Plugin<\/code> class, we defined an abstract method, <code>event_triggered<\/code>, that subclasses will need to override in order to be notified of events from the core program. <\/p>\n<p>We put this class in <code>program-root\/lib\/plugin.py<\/code>.<\/p>\n<h3>Defining the HelloWorldPlugin class<\/h3>\n<p>We then define a sample subclass of the <code>Plugin<\/code> class, <code>HelloWorldPlugin<\/code> class:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nfrom lib.plugin import Plugin\r\n\r\nclass HelloWorldPlugin(Plugin):\r\n    def event_triggered(self, event_name, resource):\r\n        print('Hello there! ', 'I got notified of: &#x5B;', event_name, '] with the resource: &#x5B;', resource, ']')\r\n<\/pre>\n<p>The <code>HelloWorldPlugin<\/code> implements the <code>event_triggered<\/code> method and prints out some text to standard output.<\/p>\n<p>We put this class in <code>program-root\/plugins\/hello-world\/hello_world_plugin.py<\/code>.<\/p>\n<h3>Defining the PluginManager class<\/h3>\n<p>The bulk of the logic of finding classes defined in Python 3 files lies in the <code>PluginManager<\/code> class:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport os\r\nimport importlib\r\n\r\nfrom lib.plugin import Plugin\r\n\r\nclass PluginManager:\r\n    \r\n    __plugins_list = &#x5B;]\r\n    \r\n    def __init__(self):\r\n        \r\n        script_folder_path = os.path.dirname(os.path.realpath(__file__))\r\n        plugin_folder_path = os.path.join(os.path.dirname(script_folder_path), 'plugins') \r\n        \r\n        for subFolderRoot, foldersWithinSubFolder, files in os.walk(os.path.basename(plugin_folder_path)):\r\n            \r\n            for file in files:\r\n                \r\n                if os.path.basename(file).endswith('.py') : \r\n                    \r\n                    # Import the relevant module (note: a module does not end with .py)\r\n                    moduleDirectory = os.path.join(subFolderRoot, os.path.splitext(file)&#x5B;0])\r\n                    moduleStr = moduleDirectory.replace(os.path.sep, '.')\r\n                    module = importlib.import_module(moduleStr)\r\n                    \r\n                    # Look for classes that implements Plugin class\r\n                    for name in dir(module) :\r\n                        obj = getattr(module, name)\r\n                        if isinstance(obj, type) and issubclass(obj, Plugin) :\r\n                            # Remember plugin\r\n                            self.__plugins_list.append(obj())\r\n                    \r\n    def notify(self, event_name, resource = ''):\r\n        for plugin in self.__plugins_list :\r\n            plugin.event_triggered(event_name, resource);\r\n<\/pre>\n<h4>The <code>__plugins_list<\/code> variable<\/h4>\n<p>The <code>PluginManager<\/code> class maintains a list of objects that extends from the Plugin class via the <code>__plugins_list<\/code> variable.<\/p>\n<h4>Inside the <code>__init__<\/code> method<\/h4>\n<p>When the <code>PluginManager<\/code> class is instantiated via the <code>__init__<\/code> method, <code>PluginManager<\/code> does the following:<\/p>\n<ol>\n<li><a href=\"http:\/\/www.techcoil.com\/blog\/how-to-get-the-directory-path-of-a-python-3-script-from-within-itself\/\" title=\"How to get the directory path of a Python 3 script from within itself\" target=\"_blank\">Gets the directory where it resides in<\/a> and make that information available via the <code>script_folder_path<\/code> variable. <\/li>\n<li>Constructs the directory path of the plugins directory and make that information available via the <code>plugin_folder_path<\/code> variable.<\/li>\n<li><a href=\"http:\/\/www.techcoil.com\/blog\/how-to-traverse-all-folders-and-files-within-a-folder-dynamically-in-python-3\/\" title=\"How to traverses all folders and files within a folder dynamically in Python 3\" target=\"_blank\">Traverse the plugin directory<\/a> and looks for Python files.\n<p>When a Python file is detected, it will use the <code><a href=\"https:\/\/docs.python.org\/3.1\/library\/importlib.html#importlib.import_module\" title=\"Python 3 reference for import lib.import_module function\" target=\"_blank\">importlib.import_module()<\/a><\/code> function to import the Python file and make it accessible via the <code>module<\/code> variable.<\/p>\n<p>It then loop through the attribute names in the module via the <code><a href=\"https:\/\/docs.python.org\/3\/library\/functions.html#dir\" title=\"Python 3 reference for dir() function\" target=\"_blank\">dir()<\/a><\/code> function. With the <code><a href=\"https:\/\/docs.python.org\/3\/library\/functions.html#getattr\" title=\"Python 3 reference for getattr function\" target=\"_blank\">getattr()<\/a><\/code> function, it checks for subclasses of the <code>Plugin<\/code> class. <\/p>\n<p>If a subclass of the <code>Plugin<\/code> class is found, it constructs an object for each <code>Plugin<\/code> subclass and append it to <code>__plugins_list<\/code>.\n<\/li>\n<\/ol>\n<h4>Inside the <code>notify<\/code> method<\/h4>\n<p>The <code>notify<\/code> method allows the core program to notify the plugins when events are generated. When the <code>notify<\/code> method is executed, the <code>event_triggered<\/code> method of each <code>Plugin<\/code> subclass will be executed.<\/p>\n<h3>Defining the script which contains the core program<\/h3>\n<p>To demonstrate the concept of looking for classes defined in Python 3 files dynamically, we create a script, <code>app.py<\/code>,  which the Python 3 executable will run:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nimport sys\r\n\r\nfrom lib.plugin_manager import PluginManager\r\n\r\nsys.path.append('.')\r\n\r\nif(__name__ == '__main__') :\r\n    \r\n    pluginManager = PluginManager()\r\n    pluginManager.notify('programStarted', 'We had gotten the program started')\r\n\r\n<\/pre>\n<p>When the script starts, an instance of <code>PluginManger<\/code> will be created. We then execute the <code>notify()<\/code> method to create the event when the core program had started.<\/p>\n<h3>Running app.py<\/h3>\n<p>With all the codes ready, we run the app.py with the Python 3 executable in our terminal shell:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\npython3 app.py\r\n<\/pre>\n<p>Doing this will result in the following output:<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nHello there!  I got notified of: &#x5B; programStarted ] with the resource: &#x5B; We had gotten the program started ]\r\n<\/pre>\n\n      <ul id=\"social-sharing-buttons-list\">\n        <li class=\"facebook\">\n          <a href=\"https:\/\/www.facebook.com\/sharer\/sharer.php?u=https%3A%2F%2Fwp.me%2Fp245TQ-7t\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n            <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Facebook.png\" alt=\"Facebook icon\"> Share\n          <\/a>\n        <\/li>\n        <li class=\"twitter\">\n          <a href=\"https:\/\/twitter.com\/intent\/tweet?text=&url=https%3A%2F%2Fwp.me%2Fp245TQ-7t&via=Techcoil_com\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Twitter.png\" alt=\"Twitter icon\"> Tweet\n          <\/a>\n        <\/li>\n        <li class=\"linkedin\">\n          <a href=\"https:\/\/www.linkedin.com\/shareArticle?mini=1&title=&url=https%3A%2F%2Fwp.me%2Fp245TQ-7t&source=https:\/\/www.techcoil.com\" target=\"_blank\" role=\"button\" rel=\"nofollow\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/linkedin.png\" alt=\"Linkedin icon\"> Share\n          <\/a>\n        <\/li>\n        <li class=\"pinterest\">\n          <a href=\"https:\/\/pinterest.com\/pin\/create\/button\/?url=https%3A%2F%2Fwww.techcoil.com%2Fblog%2Fwp-json%2Fwp%2Fv2%2Fposts%2F463&description=\" class=\"pin-it-button\" target=\"_blank\" role=\"button\" rel=\"nofollow\" count-layout=\"horizontal\">\n          <img decoding=\"async\" src=\"\/ph\/img\/3rd-party\/social-icons\/Pinterest.png\" alt=\"Pinterest icon\"> Save\n          <\/a>\n        <\/li>\n      <\/ul>\n    ","protected":false},"excerpt":{"rendered":"<p>There are times when we need to write Python codes that extends the functionality of others, especially when we are writing a framework that allows for custom extension. To allow my Python program to be extended by others via the template design pattern, the first exploration task that I did was to find out how to dynamically look for classes defined in arbitrary Python files. <\/p>\n<p>This post documents a way to look for classes defined in Python 3 files dynamically.<\/p>\n","protected":false},"author":1,"featured_media":1244,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"footnotes":""},"categories":[375],"tags":[237,236,226],"jetpack_featured_media_url":"https:\/\/www.techcoil.com\/blog\/wp-content\/uploads\/Python-Logo.gif","jetpack_shortlink":"https:\/\/wp.me\/p245TQ-7t","jetpack-related-posts":[],"jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts\/463"}],"collection":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/comments?post=463"}],"version-history":[{"count":0,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/posts\/463\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/media\/1244"}],"wp:attachment":[{"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/media?parent=463"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/categories?post=463"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.techcoil.com\/blog\/wp-json\/wp\/v2\/tags?post=463"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}