Loading repository data…
Loading repository data…
felko / repository
A lightweight library for writing modular code
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
|PyPI version| |License| |Supported Python| |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.
Let's say you want to build a modular shell, where the users can implement their own commands and prompt for example.
.. code:: python3
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:
.. code:: python3
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:
.. code:: python3
@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:
.. code:: python3
@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:
.. code:: python3
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:
.. code:: python3
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:
.. code:: python3
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:
.. code:: python3
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:
.. code:: python3
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_.
You can temporarily load a plug-in with the plugin context manager:
.. code:: python3
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:
.. code:: python3
class AbstractShell(Modular, plugins='plugins', virtual=True):
...
pip_:.. code:: bash
$ pip install modulable
And, if you're on Linux, and face a permission error, make sure to
run sudo with the -H option:
.. code:: bash
$ sudo -H pip install modulable
git_:.. code:: bash
$ git clone http://github.com/felko/modulable.git
$ cd modulable
$ sudo -H python3.4 setup.py install
Or, if you're on Windows:
.. code:: bash
$ 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 <https://github.com/felko/modulable/archive/master.zip>.
modulable is distributed under the MIT license_.
.. _pip: http://pip.readthedocs.io/ .. _example directory: https://github.com/felko/modulable/tree/master/examples .. _MIT license: http://opensource.org/licenses/MIT .. _git: https://git-scm.com/
.. |PyPI version| image:: https://img.shields.io/pypi/v/modulable.svg :target: https://pypi.python.org/pypi/modulable :alt: Latest PyPI Version .. |License| image:: https://img.shields.io/pypi/l/modulable.svg :target: https://pypi.python.org/pypi/modulable :alt: License .. |Supported Python| image:: https://img.shields.io/pypi/pyversions/modulable.svg :target: https://pypi.python.org/pypi/modulable :alt: Supported Python Versions .. |Format| image:: https://img.shields.io/pypi/format/modulable.svg :target: https://pypi.python.org/pypi/modulable :alt: Format .. |Downloads| image:: https://img.shields.io/pypi/dm/modulable.svg :target: https://pypi.python.org/pypi/modulable :alt: Downloads