summaryrefslogtreecommitdiff
path: root/content/development/exposing_function_wsme.rst
blob: da9799793d40e31d43548313c577419275569c4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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 <http://julien.danjou.info/>`_ *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
<http://legacy.python.org/dev/peps/pep-3107>`_ 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 <http://silpa.org.in>`_. 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 <http://scr.im/vasudev>`_