Skip to content

Commit 063f417

Browse files
committed
doc: add background page
This includes the details about the API design and others.
1 parent 76b09a2 commit 063f417

2 files changed

Lines changed: 234 additions & 0 deletions

File tree

doc/background.rst

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
.. _background:
2+
3+
Background
4+
==========
5+
6+
This document describes what design decisions were made during the development
7+
of this library. It also describes how the library works in detail.
8+
9+
This document assumes that you can use the library and have at least limited
10+
knowledge about the source code. If you feel that it is not true for you, you
11+
may want to read the :ref:`tutorial` and :ref:`howto`.
12+
13+
14+
API design
15+
----------
16+
17+
The API should be simple for use to simple cases, but also provide great
18+
flexibility for the advanced cases. When increasing flexibility of the API it
19+
should not change the simple API unless it is absolutely required.
20+
21+
API compatibility is paramount. API breaking is only allowed when it is on par
22+
with the the the gain of the new functionality.
23+
24+
Adding new parameters to functions which have default value is not considered a
25+
breaking API change.
26+
27+
28+
Simple API
29+
~~~~~~~~~~
30+
31+
API should be kept as simple as possible. It means that describing an expected
32+
request and its response should be trivial for the user. For this reason, the
33+
API is flat: it contains a handful of functions which have many parameters
34+
accepting built-in python types (such as bytes, string, int, etc) in contrast
35+
to more classes and functions with less arguments.
36+
37+
This API allows to define an expected request and the response which will be
38+
sent back to the client in a single line. This is one of the key features so
39+
using the library is not complicated.
40+
41+
Example:
42+
43+
.. code-block:: python
44+
45+
def test_query_params(httpserver):
46+
httpserver.expect_request("/foo", query_string={"user": "user1"}).respond_with_data("OK")
47+
48+
It is simple in the most simple cases, but once the expectation is more
49+
specific, the line can grow significantly, so here the user is expected to put
50+
the literals into variables:
51+
52+
.. code-block:: python
53+
54+
def test_query_params(httpserver):
55+
httpserver.expect_request("/foo", query_string=expected_query).respond_with_data("OK")
56+
57+
58+
If the user wants something more complex, classes are available for this which
59+
can be instantiated and then specified for the parameters normally accepting
60+
only built-in types.
61+
62+
The easy case should be made easy, with the possibility of making advanced
63+
things in a bit more complex way.
64+
65+
Flexible API
66+
~~~~~~~~~~~~
67+
68+
The API should be also made flexible as possible but it should not break the
69+
simple API and not make the simple API complicated. A good example for this is
70+
the `respond_with_handler` method, which accepts a callable object (eg. a
71+
function) which receives the request object and returns the response object.
72+
73+
The user can implement the required logic there.
74+
75+
Adding this flexibility however did not cause any change in the simple API, the
76+
simple cases can be still used as before.
77+
78+
79+
Higher-level API
80+
~~~~~~~~~~~~~~~~
81+
82+
In the early days of this library, it wanted to support the low-level http
83+
protocol elements: request status, headers, etc to provide full coverage for the
84+
protocol itself. This was made in order to made the most advanced customizations
85+
possible.
86+
87+
Then the project received a few PRs adding `HeaderValueMatcher` and support for
88+
authorization which relied on the low-level API to add a higher-level API
89+
without breaking it. In the opposite case, adding a low-level API to a
90+
high-level would not be possible.
91+
92+
Transparency
93+
~~~~~~~~~~~~
94+
95+
The API provided by *pytest-httpserver* is transparent. That means that the
96+
objects (most importantly the `Request` and `Response` objects) defined by
97+
*werkzeug* are visible by the user of *pytest-httpserver*, there is no wrapping
98+
made. This is done by the sake of simplicity.
99+
100+
As *werkzeug* provides a stable API, there's no need to change this in the
101+
future, however this also limits the library to stick with *werkzeug* in the
102+
long term. Replacing *werkzeug* to something else would break the API due to
103+
this transparency.
104+
105+
Requirements
106+
------------
107+
108+
This section describes how to work with pytest-httpserver's requirements.
109+
These are the packages used by the library.
110+
111+
Number of requirements
112+
~~~~~~~~~~~~~~~~~~~~~~
113+
114+
It is required to keep the requirements at minimum. When adding a new library to
115+
the package requirements, research in the following topics should be done:
116+
117+
* code quality
118+
* activity of the development and maintenance
119+
* number of open issues, and their content
120+
* how many people using that library
121+
* python interpreter versions supported
122+
* amount of API breaking changes
123+
* license
124+
125+
Sometimes, it is better to have the own implementation instead of having a tiny
126+
library added to the requirements, which may cause compatibility issues.
127+
128+
129+
Requirements version restrictions
130+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131+
132+
In general, the package requirements should have no version restrictions. For
133+
example, the *werkzeug* library has no restrictions, which means that if a new
134+
version comes out of it, it is assumed that *pytest-httpserver* will be able to
135+
run with it.
136+
137+
Many people uses this library in an environment having full of other packages
138+
and limiting version here will limit their versions in their requirements also.
139+
For example if there's a software using *werkzeug* `1.0.0` and our requirements
140+
have `<0.9` specified it will make *pytest-httpserver* incompatible with their
141+
software.
142+
143+
144+
Requirements testing
145+
~~~~~~~~~~~~~~~~~~~~
146+
147+
Currently it is required to test with only the latest version of the required
148+
packages. However, if there's an API breaking change which affects
149+
*pytest-httpserver*, a decision should be made:
150+
151+
* apply version restrictions, possibly making *pytest-httpserver* incompatible
152+
with some other software
153+
154+
* add workaround to *pytest-httpserver* to support both APIs
155+
156+
157+
HTTP server
158+
-----------
159+
160+
The chosen HTTP server which drives this library is imlemented by the *werkzeug*
161+
library. The reason behind this decision is that *werkzeug* is used by Flask, a
162+
very popular web framework and it provides a proven, stable API in the long
163+
term.
164+
165+
Supported python versions
166+
-------------------------
167+
168+
Supporting the latest python versions (such as 3.7 and 3.8 at the time of
169+
writing this), is a must. Supporting the older versions is preferred, following
170+
the state of the officially supported python versions by PSF.
171+
172+
The library should be tested periodically on the supported versions.
173+
174+
Dropping support for old python versions is possible if supporting would cause
175+
an issue or require extensive workaround. Currently, 3.4 is still supported by
176+
the library, however it is deprecated by PSF. As it causes no problems for
177+
*pytest-httpserver* (there's an additional requirement for this in the setup.py,
178+
but that's all), the support for this version will be maintained as long as
179+
possible. Once a new change is added to the library which require great effort
180+
to maintain compatibility with 3.4, the support for it will be dropped.
181+
182+
183+
Testing and coverage
184+
--------------------
185+
186+
It is not required to have 100% test coverage but all possible use-cases should
187+
be covered. Github actions is used to test the library on all the supported
188+
python versions, and tox.ini is provided if local testing is desired.
189+
190+
When a bug is reported, there should be a test for it, which would re-produce
191+
the error and it should pass with the fix.
192+
193+
Server starting and stopping
194+
----------------------------
195+
196+
The server is started when the first test is run which uses the httpserver
197+
fixture. It will be running till the end of the session, and new tests will use
198+
the same instance. A cleanup is done between the tests which restores the clean
199+
state (no handlers registered, empty log, etc) to avoid cross-talk.
200+
201+
The reason behind this is the time required to stop the server. For some reason,
202+
*werkzeug* (the http server used) needs about 1 second to stop itself. Adding this
203+
time to each test is not acceptable in most of the cases.
204+
205+
Note that it is still compatible with *pytest-xdist* (a popular pytest extension
206+
to run the tests in parallel) as in such case, distinct test sessions will be
207+
run and those will have their own http server instance.
208+
209+
210+
Fixture scope
211+
-------------
212+
213+
Due to the nature of the http server (it is run only once), it seems to be a
214+
good recommendation to keep the httpserver fixture session scoped, not function
215+
scoped. The problem is that the cleanup which needs to be done between the
216+
tests (as the server is run only once, see above), and that cleanup needs to be
217+
attached to a function scoped fixture.
218+
219+
HTTP port selection
220+
-------------------
221+
222+
In early versions of the library, the user had to specify which port the server
223+
should be bound. This later changed to have an so-called ephemeral port, which
224+
is a random free port number chosen by the kernel. It is good because it
225+
guarantees that it will be available and it allows parallel test runnings for
226+
example.
227+
228+
In some cases it is not desired (eg if the code being tested has wired-in port
229+
number), in such cases it is still possible to specify the port number.
230+
231+
Also, the host can be specified which allows to bind on "0.0.0.0" so the server
232+
is accessible from the network in case you want to test a javascript code
233+
running on a different server in a browser.

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@ For further details, please read the :doc:`guide` or the :doc:`api`.
3939
tutorial
4040
howto
4141
api
42+
background
4243
changes

0 commit comments

Comments
 (0)