Re-architecture of the pythonizr builder

Chain of Responsibility in Python

In this story, I’ve explained why and how I moved from the anti-pattern to the chain of responsibility for the pythonizr.com builder.

Pythonizr Buider

pythonizr builder code was written using simple functional programming which was served by the flask — a micro web framework written in python.

The basic implementation was just to grab required file templates and zip all together in a project folder. This was done using separate function for each file/package requested.

...
func = {'main_py': main_py, 'setup_py': setup_py, 'readme_rst': readme_rst, 'requirements_txt': requirements_txt, 'manifest_in': manifest_in, 'gitignore': gitignore, 'config_handler': config_handler, 'argparse': arg_parse, 'mit_license': mit_license, 'apache_license': apache_license, 'gnu_license': gnu_license, 'empty_license': empty_license}
...

Each argument is mapped to a single function. Life is good.

The builder function would then execute the respective functions for requested argument which dumps files/packages and in a zip.

for req in request.args:
if req in func:
func[req](zip_file)

Here, all the functions were independent of each other. i.e, main_py adds main.py file with a basic template and gitignore adds .gitignore file.

def main_py(zip_file):
zip_file.writestr('main.py', main_py_template)
def gitignore(zip_file):
zip_file.writestr('.gitignore', gitignore_template)

The Problem

The problem of dependency started coming once the construction of a file/package became dependent on other argument. i.e, the function for the MANIFEST.in file is dependent on license.txt file.

def manifest_in(zip_file):
manifest_content = ''

global is_license
if is_license:
manifest_content += 'include LICENSE.txt'

zip_file.writestr('MANIFEST.in', manifest_content)

MANIFEST.in file adds ``include LICENSE.txt``, only if it contains in the zip package. Here, I have to maintain global flag prior to execute this function to know whether to include the line or not.

This was jut the beginning, sooner the more and more inter-dependency started arising once I started adding improvements. I might have to keep all the flags at the beginning even for the simple construction.

The Solution

I thought of the Instagram filters where each filter adds their respective layer on the image and pass on to the next filter.

The solution I’ve made is the same where each builder function would be a filter which will add respective file into the zip and pass on the zip to next filter. The inter-dependency will be handled by the respective filter inside itself.

The following is the base filter class:

class BaseFilter():
"""
Define an interface for handling requests.
Implement the successor link.
"""

def __init__(self, successor=None):
self._successor = successor

@abc.abstractmethod
def handle_request(self, zip_file, args):
pass

Note that handle_request is an abstract method every filter has to implement.

Following is the example of the Helper filter:

class HelperFilter(BaseFilter):

def handle_request(self, zip_file, args):

if 'gitignore' in args:
add_gitignore(zip_file)

if 'config_handler' in args:
add_config_handler(zip_file)

if 'argparse' in args:
add_argparse(zip_file)

# handle successor if provided
if self._successor is not None:
self._successor.handle_request(zip_file, args)

All I had to do is to create a chain of responsibility of such filters:

# Create filter chain
license_handler = LicenseFilter()
helper_handler = HelperFilter(license_handler)
basic_files_handler = BasicFilesFilter(helper_handler)
# start the process
basic_files_handler.handle_request(zip_file, request.args)

Now, If there is any dependency for the files, the filter can handle it without keeping any global flags.

if 'manifest_in' in args:
manifest_content = ''
if 'no_license' not in args:
manifest_content += 'include LICENSE.txt'

add_manifest_in(zip_file, manifest_content)

Please have a look at the full source code at github and feel free to comment.

Reference: chain of responsibility

An Idea that worked on…

If you like this post, please click the clap 👏button below a few times to show your support!

--

--

SDE 2 @Amazon. Here to share the knowledge and experiences. https://akashpanchal.com

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store