Skip to main content

Latest PyPI Version License Supported Python Versions Format Downloads

modular.py is a small library that helps you write a modular and maintainable codebase.

The library works with class: the loaded plug-ins are “injected” in the given class. You can specify the way of injection, either by stacking functions, with the modulable decorator, by overloading the base method, with the overridable decorator, and finally with the alternative decorator, which runs every function until it finds one that doesn’t raise an exception.

Those decorators conserve the original method’s informations, such as name, module, docstring, and annotations.

Example

Let’s say you want to build a modular shell, where the users can implement their own commands and prompt for example.

from modular import *

class Shell(Modular, plugin_directory='plugins'):

This declares a modular class, whose plug-ins are in the plugins directory relative to the current working directory.

The library will load every plug-in (must be a .py file) in that directory when the class is instantiated.

It is convenient to declare a init method, called within the real __init__ constructor, to allow users to initialize their plug-in specific attributes:

def __init__(self, *args, **kwds):
    self.init(*args, **kwds)

@modulable
def init(self, *args, **kwds):
    self.running = False

Here, we decorate the init method with modulable. That means every plug-ins’ implementations of the init method will be executed with the given arguments.

Next, we want a function that is executed between every command, and, say, a method that returns the shell prompt:

@modulable
def update(self):
    pass

@overridable
def prompt(self):
    return '> '

This time, we use the overridable decorator. This decorator uses the last implementation of prompt loaded.

Lastly, we want a function that reacts on user input. What we want here is to run every implementation until one works (i.e. doesn’t raise an error). To do that, you can use the alternative decorator which takes an exception type and calls every implementation one-by-one until one doesn’t raise an error of this type:

@alternative(ValueError)
def react(self, i):
    if i:
        print('Unrecognized command:', repr(i))

We provide a default case here, if there isn’t any implementation working.

Finally, we define some non-modulable methods to make the whole thing works:

def run(self):
    self.running = True

    while self.running:
        i = input(self.prompt())
        self.react(i)
        self.update()

For instance, our shell doesn’t implement any plug-in. Just for the example, we’ll implement a quit plug-in, which stops the shell when the user types in quit, a greet plug-in, and finally we’ll customize our prompt.

The implementation of the quit command is pretty straight-forward:

def react(self, i):
    if i == 'quit':
        self.running = False
    else:
        raise ValueError

By raising ValueError, we delegate the input processing to the next implementation of react.

The greet plug-in does the same, with a bit more complex parsing:

def react(self, i):
    lexemes = i.split()

    try:
        if lexemes[0] == 'greet':
            print('Hey', lexemes[1], '!')
        else:
            raise ValueError
    except IndexError:
        raise ValueError

Finally, lets define a prompt that displays the command count:

def init(self, *args, **kwds):
    self.command_count = 0

def update(self):
    self.command_count += 1

def prompt(self):
    return '[{}]: '.format(self.command_count)

The plug-ins must be contained in the specified plug-in directory in the class declaration, here, plugins. You should have a similar directory tree:

.
├── plugins
│   ├── command_count_prompt.py
│   ├── greet.py
│   └── quit.py
└── shell.py

To use this class, simply instantiate a Shell object and call its run method:

sh = Shell()
sh.run()

Here’s what it does:

[0]:
[1]:
[2]: greet Jonathan
Hey Jonathan !
[3]:
[4]:
[5]: unknown command
Unrecognized command: 'unknown command'
[6]:
[7]: quit

You can see the complete code in the example directory.

Advanced use

You can temporarily load a plug-in with the plugin context manager:

with Shell.plugin('greet'):
    sh.run()

You can also check the loaded plug-ins by typing Shell.loaded_plugins.

Finally, there is an optional virtual keyword argument at class definition. virtual is set to False by default, but if set to True, the class will not load the plug-ins automatically:

class AbstractShell(Modular, plugins='plugins', virtual=True):
    ...

Installation

$ pip install modulable

And, if you’re on Linux, and face a permission error, make sure to run sudo with the -H option:

$ sudo -H pip install modulable
$ git clone http://github.com/felko/modulable.git
$ cd modulable
$ sudo -H python3.4 setup.py install

Or, if you’re on Windows:

$ git clone http://github.com/felko/modulable.git
$ cd modulable
$ py -3.4 setup.py install

If you don’t have git, you can download the zip file here.

License

modulable is distributed under the MIT license.

Release files for modulable 1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for modulable 1.0
File Size Uploaded
modulable-1.0.zip 11.0 kB Details

Release files / modulable-1.0.zip

Download URL modulable-1.0.zip
Size 11.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3ba8e49194a7545313494451587b678b87ebce7cb7dab26d23c9dca74c68c7c2
BLAKE2b-256 checksum
How to use checksums
d98257992c779a18f962f10f633259f09bbd281c947fe9cac877a2f56f22386a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release history Release notifications | RSS feed

This release

1.0 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page