File: faq.rst

package info (click to toggle)
python-aiohttp 1.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,288 kB
  • ctags: 4,380
  • sloc: python: 27,221; makefile: 236
file content (367 lines) | stat: -rw-r--r-- 11,712 bytes parent folder | download
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
Frequently Asked Questions
==========================
.. contents::
   :local:

Are there any plans for @app.route decorator like in Flask?
-----------------------------------------------------------
There are couple issues here:

* This adds huge problem name "configuration as side effect of importing".
* Route matching is order specific, it is very hard to maintain import order.
* In semi large application better to have routes table defined in one place.

For this reason feature will not be implemented. But if you really want to
use decorators just derive from web.Application and add desired method.


Has aiohttp the Flask Blueprint or Django App concept?
------------------------------------------------------

If you're planing to write big applications, maybe you must consider
use nested applications. They acts as a Flask Blueprint or like the
Django application concept.

Using nested application you can add sub-applications to the main application.

see: :ref:`aiohttp-web-nested-applications`.


How to create route that catches urls with given prefix?
---------------------------------------------------------
Try something like::

    app.router.add_route('*', '/path/to/{tail:.+}', sink_handler)

Where first argument, star, means catch any possible method
(*GET, POST, OPTIONS*, etc), second matching ``url`` with desired prefix,
third -- handler.


Where to put my database connection so handlers can access it?
--------------------------------------------------------------

:class:`aiohttp.web.Application` object supports :class:`dict`
interface, and right place to store your database connections or any
other resource you want to share between handlers. Take a look on
following example::

    async def go(request):
        db = request.app['db']
        cursor = await db.cursor()
        await cursor.execute('SELECT 42')
        # ...
        return web.Response(status=200, text='ok')


    async def init_app(loop):
        app = Application(loop=loop)
        db = await create_connection(user='user', password='123')
        app['db'] = db
        app.router.add_get('/', go)
        return app


Why the minimal supported version is Python 3.4.2
--------------------------------------------------

As of aiohttp **v0.18.0** we dropped support for Python 3.3 up to
3.4.1.  The main reason for that is the :meth:`object.__del__` method,
which is fully working since Python 3.4.1 and we need it for proper
resource closing.

The last Python 3.3, 3.4.0 compatible version of aiohttp is
**v0.17.4**.

This should not be an issue for most aiohttp users (for example `Ubuntu`
14.04.3 LTS provides python upgraded to 3.4.3), however libraries
depending on aiohttp should consider this and either freeze aiohttp
version or drop Python 3.3 support as well.

As of aiohttp **v1.0.0** we dropped support for Python 3.4.1 up to
3.4.2+ also. The reason is: `loop.is_closed` appears in 3.4.2+

Again, it should be not an issue at 2016 Summer because all major
distributions are switched to Python 3.5 now.


How a middleware may store a data for using by web-handler later?
-----------------------------------------------------------------

:class:`aiohttp.web.Request` supports :class:`dict` interface as well
as :class:`aiohttp.web.Application`.

Just put data inside *request*::

   async def handler(request):
       request['unique_key'] = data

See https://github.com/aio-libs/aiohttp_session code for inspiration,
``aiohttp_session.get_session(request)`` method uses ``SESSION_KEY``
for saving request specific session info.


.. _aiohttp_faq_parallel_event_sources:

How to receive an incoming events from different sources in parallel?
---------------------------------------------------------------------

For example we have two event sources:

   1. WebSocket for event from end user

   2. Redis PubSub from receiving events from other parts of app for
      sending them to user via websocket.

The most native way to perform it is creation of separate task for
pubsub handling.

Parallel :meth:`aiohttp.web.WebSocketResponse.receive` calls are forbidden, only
the single task should perform websocket reading.

But other tasks may use the same websocket object for sending data to
peer::

    async def handler(request):

        ws = web.WebSocketResponse()
        await ws.prepare(request)
        task = request.app.loop.create_task(
            read_subscription(ws,
                              request.app['redis']))
        try:
            async for msg in ws:
                # handle incoming messages
                # use ws.send_str() to send data back
                ...

        finally:
            task.cancel()

    async def read_subscriptions(ws, redis):
        channel, = await redis.subscribe('channel:1')

        try:
            async for msg in channel.iter():
                answer = process message(msg)
                ws.send_str(answer)
        finally:
            await redis.unsubscribe('channel:1')


.. _aiohttp_faq_terminating_websockets:

How to programmatically close websocket server-side?
----------------------------------------------------


For example we have an application with two endpoints:


   1. ``/echo`` a websocket echo server that authenticates the user somehow
   2. ``/logout_user`` that when invoked needs to close all open
      websockets for that user.

Keep in mind that you can only ``.close()`` a websocket from inside
the handler task, and since the handler task is busy reading from the
websocket, it can't react to other events.

One simple solution is keeping a shared registry of websocket handler
tasks for a user in the :class:`aiohttp.web.Application` instance and
``cancel()`` them in ``/logout_user`` handler::

    async def echo_handler(request):

        ws = web.WebSocketResponse()
        user_id = authenticate_user(request)
        await ws.prepare(request)
        request.app['handlers'][user_id].add(asyncio.Task.current_task())

        try:
            async for msg in ws:
                # handle incoming messages
                ...

        except asyncio.CancelledError:
            print('websocket cancelled')
        finally:
            request.app['handlers'][user_id].remove(asyncio.Task.current_task())
        await ws.close()
        return ws

    async def logout_handler(request):

        user_id = authenticate_user(request)

        for task in request.app['handlers'][user_id]:
            task.cancel()

        # return response
        ...

    def main():
        loop = asyncio.get_event_loop()
        app = aiohttp.web.Application(loop=loop)
        app.router.add_route('GET', '/echo', echo_handler)
        app.router.add_route('POST', '/logout', logout_handler)
        app['websockets'] = defaultdict(set)
        aiohttp.web.run_app(app, host='localhost', port=8080)


How to make request from a specific IP address?
-----------------------------------------------

If your system has several IP interfaces you may choose one which will
be used used to bind socket locally::

    conn = aiohttp.TCPConnector(local_addr=('127.0.0.1, 0), loop=loop)
    async with aiohttp.ClientSession(connector=conn) as session:
        ...

.. seealso:: :class:`aiohttp.TCPConnector` and ``local_addr`` parameter.


.. _aiohttp_faq_tests_and_implicit_loop:


How to use aiohttp test features with code which works with implicit loop?
--------------------------------------------------------------------------

Passing explicit loop everywhere is the recommended way.  But
sometimes, in case you have many nested non well-written services,
this is impossible.

There is a technique based on monkey-patching your low level service
that depends on aioes, to inject the loop at that level. This way, you
just need your ``AioESService`` with the loop in its signature. An
example would be the following::

  import pytest

  from unittest.mock import patch, MagicMock

  from main import AioESService, create_app

  class TestAcceptance:

      async def test_get(self, test_client, loop):
          with patch("main.AioESService", MagicMock(
                  side_effect=lambda *args, **kwargs: AioESService(*args,
                                                                   **kwargs,
                                                                   loop=loop))):
              client = await test_client(create_app)
              resp = await client.get("/")
              assert resp.status == 200

Note how we are patching the ``AioESService`` with and instance of itself but
adding the explicit loop as an extra (you need to load the loop fixture in your
test signature).

The final code to test all this (you will need a local instance of
elasticsearch running)::

  import asyncio

  from aioes import Elasticsearch
  from aiohttp import web


  class AioESService:

      def __init__(self, loop=None):
          self.es = Elasticsearch(["127.0.0.1:9200"], loop=loop)

      async def get_info(self):
          return await self.es.info()


  class MyService:

      def __init__(self):
          self.aioes_service = AioESService()

      async def get_es_info(self):
          return await self.aioes_service.get_info()


  async def hello_aioes(request):
      my_service = MyService()
      cluster_info = await my_service.get_es_info()
      return web.Response(text="{}".format(cluster_info))


  def create_app(loop=None):

      app = web.Application(loop=loop)
      app.router.add_route('GET', '/', hello_aioes)
      return app


  if __name__ == "__main__":
      web.run_app(create_app())


And the full tests file::


  from unittest.mock import patch, MagicMock

  from main import AioESService, create_app


  class TestAioESService:

      async def test_get_info(self, loop):
          cluster_info = await AioESService("random_arg", loop=loop).get_info()
          assert isinstance(cluster_info, dict)


  class TestAcceptance:

      async def test_get(self, test_client, loop):
          with patch("main.AioESService", MagicMock(
                  side_effect=lambda *args, **kwargs: AioESService(*args,
                                                                   **kwargs,
                                                                   loop=loop))):
              client = await test_client(create_app)
              resp = await client.get("/")
              assert resp.status == 200

Note how we are using the ``side_effect`` feature for injecting the loop to the
``AioESService.__init__`` call. The use of ``**args, **kwargs`` is mandatory
in order to propagate the arguments being used by the caller.


API stability and deprecation policy
------------------------------------

aiohttp tries to not break existing users code.

Obsolete attributes and methods are marked as *deprecated* in
documentation and raises :class:`DeprecationWarning` on usage.

Deprecation period is usually a year and half.

After the period is passed out deprecated code is be removed.

Unfortunately we should break own rules if new functionality or bug
fixing forces us to do it (for example proper cookies support on
client side forced us to break backward compatibility twice).

All *backward incompatible* changes are explicitly marked in
:ref:`CHANGES <aiohttp_changes>` chapter.


How to enable gzip compression globally for the whole application?
------------------------------------------------------------------

It's impossible. Choosing what to compress and where don't apply such
time consuming operation is very tricky matter.

If you need global compression -- write own custom middleware. Or
enable compression in NGINX (you are deploying aiohttp behind reverse
proxy, isn't it).


.. disqus::
  :title: aiohttp FAQ