File: video_tutorial.rst

package info (click to toggle)
quart 0.20.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,888 kB
  • sloc: python: 8,644; makefile: 42; sh: 17; sql: 6
file content (184 lines) | stat: -rw-r--r-- 4,896 bytes parent folder | download | duplicates (2)
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
.. _video_tutorial:

Tutorial: Serving video
=======================

In this tutorial we will build a very basic video server. It will
serve a video directly.

This tutorial is meant to serve as an introduction to serving large
files with conditional responses in Quart. If you want to skip to the
end the code is on `Github
<https://github.com/pallets/quart/tree/main/examples/video>`_.

1: Creating the project
-----------------------

We need to create a project for our video server, I like to use
`Poetry <https://python-poetry.org>`_ to do this. Poetry is installed
via pip (or via `Brew <https://brew.sh/>`_):

.. code-block:: console

    pip install poetry

We can then use Poetry to create a new video project:

.. code-block:: console

    poetry new --src video

Our project can now be developed in the *video* directory, and all
subsequent commands should be in run the *video* directory.

2: Adding the dependencies
--------------------------

We only need Quart to build this simple video server, which we can
install as a dependency of the project by running the following:

.. code-block:: console

    poetry add quart

Poetry will ensure that this dependency is present and the paths are
correct by running:

.. code-block:: console

    poetry install

3: Creating the app
-------------------

We need a Quart app to be our web server, which is created by the
following addition to *src/video/__init__.py*:

.. code-block:: python
    :caption: src/video/__init__.py

    from quart import Quart

    app = Quart(__name__)

    def run() -> None:
        app.run()

To make the app easy to run we can call the run method from a poetry
script, by adding the following to *pyproject.toml*:

.. code-block:: toml
    :caption: pyproject.toml

    [tool.poetry.scripts]
    start = "video:run"

Which allows the following command to start the app:

.. code-block:: console

    poetry run start

4: Serving the UI
-----------------

When users visit our website we will show them the same video served
directly, and via chunks. The following HTML template should be added
to *src/video/templates/index.html*:

.. code-block:: html
    :caption: src/video/templates/index.html

    <video controls width="100%">
      <source src="/video.mp4" type="video/mp4">
    </video>

This is a very basic UI in terms of the styling.

We can now serve this template for the root path i.e. ``/`` by adding
the following to *src/video/__init__.py*:

.. code-block:: python

    from quart import render_template

    @app.get("/")
    async def index():
        return await render_template("index.html")

5: Implementing the routes
--------------------------

As we are serving a large file we should allow for conditional
responses. This is where the data returned in the response is
conditional on what the request asked for. This is done via the
``Range`` header field which can be inspected via the
``request.range`` attribute.

Quart has in-built methods to make a response conditional on the
request range. The first is to use the conditional argument when
sending a file, the second is to use the response ``make_conditional``
method. The former is shown below, which should be added to
*src/video/__init__.py*:

.. code-block:: python
    :caption: src/video/__init__.py

    @app.route("/video.mp4")
    async def auto_video():
        return await send_file(app.static_folder / "video.mp4", conditional=True)

6: Testing
----------

To test our app we need to check that the full video is returned
unless a conditional range request is made. This is done by adding the
following to *tests/test_video.py*:

.. code-block:: python
    :caption: tests/test_video.py

    from video import app

    async def test_auto_video() -> None:
        test_client = app.test_client()
        response = await test_client.get("/video.mp4")
        data = await response.get_data()
        assert len(data) == 255_849

        response = await test_client.get("/video.mp4", headers={"Range": "bytes=200-1000"})
        data = await response.get_data()
        assert len(data) == 801

As the test is an async function we need to install `pytest-asyncio
<https://github.com/pytest-dev/pytest-asyncio>`_ by running the
following:

.. code-block:: console

    poetry add --dev pytest-asyncio

Once installed it needs to be configured by adding the following to
*pyproject.toml*:

.. code-block:: toml

    [tool.pytest.ini_options]
    asyncio_mode = "auto"

Finally we can run the tests via this command:

.. code-block:: console

    poetry run pytest tests/

If you are running this in the Quart example folder you'll need to add
a ``-c pyproject.toml`` option to prevent pytest from using the Quart
pytest configuration.

7: Summary
----------

We've built a server that will serve large files conditionally as
requested by the client, including the ability to limit the maximum
partial size.