Exposing function in python module using entry_points, WSME in a Flask webapp ############################################################################# :date: 2014-06-07 18:10 :slug: entry_point_wsme.rst :tags: python, programming, wsme, rest, setuptools :author: copyninja :summary: In this post I try to explain how to expose a function in a python module as REST service using WSME from an Flask application using setuptools entry_points feature. The heading might be ambiguous, but I couldn't figure out better heading so let me start by explaining what I'm trying to solve here. Problem ------- I have a python module which contains a function which I want to expose as a REST web service in a Flask application. I use WSME for Flask application, which actually needs signature of function in question and problem comes to picture because function to be exposed is foreign to Flask application, it resides in separate python module. Solution -------- While reading `Julien Danjou's `_ *Hackers Guide To Python* book I came across the setuptools `entry_points` concept which can be used to extend existing feature of a tool like plug-ins. So here I'm going to use this `entry_points` feature from setuptools to provide function in the module which can expose the signature of function[s] to be exposed through REST. Of course this means I need to modify module in question to write entry_points and function for giving out signature of function to be exposed. I will explain this with small example. I have a dummy module which provides a add function and a function which exposes the add functions signature. .. code-block:: python def add(a, b): return a + b def expose_rest_func(): return [add, int, int, int] This is stored in `dummy/__init__.py` file. I use `pbr` tool to package my python module. Below is content of `setup.cfg` file. .. code-block:: python [metadata] name = dummy author = Vasudev Kamath author-email = kamathvasudev@gmail.com summary = Dummy module for testing purpose version = 0.1 license = MIT description-file = README.rst requires-python = >= 2.7 [files] packages = dummy [entry_points] = myapp.api.rest = rest = dummy:expose_rest_func The special thing in above file is `entry_points` section, which defines function to be hooked into entry_point. In our case entry_point `myapp.api.rest` is used by our Flask application to interact with modules which expose them. The function which will be got accessing the entry_point is `expose_rest_func` which gives the function to be exposed its arg types and return types as a list. If we are looking at only supporting python3 it was sufficient to know function name only and use `function annotations `_ in function definition. Since I want to support both python2 and python3 this is out of question. Now, just run the following command in virtualenv to get the module installed. .. code-block:: shell-session PBR_VERSION=0.1 python setup.py sdist pip install dist/dummy_module-0.1.tar.gz Now if you want to see if the module is exposing entry_point or not just use `entry_point_inspector` tool after installing you will get a command called `epi` if you run it as follows you should note the dummy_module in its output .. code-block:: shell-session epi group list +-----------------------------+ | Name | +-----------------------------+ | cliff.formatter.completion | | cliff.formatter.list | | cliff.formatter.show | | console_scripts | | distutils.commands | | distutils.setup_keywords | | egg_info.writers | | epi.commands | | flake8.extension | | setuptools.file_finders | | setuptools.installation | | myapp.api.rest | | stevedore.example.formatter | | stevedore.test.extension | | wsme.protocols | +-----------------------------+ So our entry_point is exposed now, we need to access it in our Flask application and expose the function using WSME. It is done by below code. .. code-block:: python from wsmeext.flask import signature import flask import pkg_resources def main(): app = flask.Flask(__name__) app.config['DEBUG'] = True for entrypoint in pkg_resources.iter_entry_points('myapp.api.rest'): # Ugly but fix is only supporting python3 func_signature = entrypoint.load()() app.route('/' + func_signature[0].__name__, methods=['POST'])( signature(func_signature[-1], *func_signature[1:-1])(func_signature[0])) app.run() if __name__ == '__main__': main() entry_point `myapp.api.rest` are iterated using the pkg_resources package provided by setuptools, when I load the entry_point I get back the function to be used which is called in same place to get function signature. Then I'm calling Flask and WSME decorator functions (yeah instead of decorating I'm using them directly over function to be exposed). The code looks bit ugly at the place where I'm accessing list using slices but I can't help it due to limitation of python2 with python3 there is new packing and unpacking stuff which makes code look bit more cooler see below. .. code-block:: python from wsmeext.flask import signature import flask import pkg_resources def main(): app = flask.Flask(__name__) app.config['DEBUG'] = True for entrypoint in pkg_resources.iter_entry_points('silpa.api.rest'): func, *args, rettype = entrypoint.load()() app.route('/' + func.__name__, methods=['POST'])( signature(rettype, *args)(func_signature[0])) app.run() if __name__ == '__main__': main() You can access the service at `http://localhost:5000/add` depending on `Accept` header of HTTP you will get either XML or JSON response. If you access it from browser you will get XML response. Usecase ------- Now if you are wondering what is the reason behind this experience, this is for `SILPA Project `_. I'm trying to implement REST service for all Indic language computing module. Since all these module are independent of SILPA which is a Flask web app I had to find a way to achieve this, and this is what I came up with. Conclusion ---------- I'm not sure if there is any other approaches to achieve this, if there I would love to hear about them. You can write your comments and suggestion over `email `_