For example, creation of the stub returning us Hello World:
import requests
from restub import Service
with Service(routes=['GET', r'/$', 'Hello world']) as srv:
requests.get(srv.host)
Often while developing there is a need to create some HTTP stub. Frequent solutions of this problem among developers are:
- Using shareware and non - free software, like SoapUI
- Creating service from scratch, for example, using a Flask
- For unit - tests, using such packages as requests - mock
Each of these solutions has the right for life. I needed free, completely transparent and simple package creating a really existing server in system. A server that could be invoked both from code and applications, like browser or curl. I found a lot of similar libraries, but for various reasons they did not fit me. So the restub appeared.
Main functionality:
- Automatic addition of necessary headers according to a contents type
- Tracing of an output of requests and responses
- Support of regular expressions in URL
- Emulation of a slow connection
- Support of HTTPS
A route represents the ordered sequence of values(method, path, data, headers, status) describing data which we can receive at the specified address and a method of access. Therefore the method of access and the address is a required and other values can be omitted.
method
— an access method, can be "GET", "POST", "PUT" or "DELETE"
path
— describing the response address, can be regex
* data
— response data, can be str or dict
* headers
— HTTP response headers
* status
— code of the response status
When data passed the headers Content - type and Content - length will be automatically added in response. Of course, you can always override these headers. Having sent the dict as data the header 'Content-type' with the value 'application/json' will be added. When str passed, the following scenarios are possible:
- If the str is a path to the file existing in system, contents of this file will be load in a body of response. At the same time, if the extension of the file has a matching with one of CTYPES values(the dictionary containing often used formats of data, such as “css”, “js”, “ttf”, etc), the Content - type will be taken there
- If the str represents json, xml or html document, then the Content - type will have the corresponding values: 'application/json', 'application/xml' or 'text/html'
- In all other cases, data will be transferred as 'text/plain'
The stub can be run as a context manager, a decorator of function or as a class instance. Before the run of a stub at least one route has to be defined. The address where the stub is started can be received through the property - host. By default the stub is available at the address http://localhost:8081 or https://localhost:8081 if the secure mode was enabled.
Run as the a context manager with a change of port:
from restub import Service
with Service(routes=['GET', r'/$'], port=7777) as srv:
# your requests here
Run as the decorator of function:
from restub import Service
@Service(routes=['GET', r'/$'])
def stubbed_func():
# your requests here
Run as the class instance:
from restub import Service
srv = Service(routes=['GET', r'/$'])
srv.start()
# your requests here
srv.stop()
Run as the class instance and definition of routes through the functions of the same name:
from restub import Service
srv = Service()
srv.get(r'/$') # post(..), put(..), delete(..)
srv.start()
# your requests here
srv.stop()
For work with HTTPS it is necessary to set secure flag in True and pass absolute paths to a private key and a certificate:
from restub import Service
with Service(routes=['GET', r'/$'], secure=True, crt='<abs path to key>', key='<abs path to cert>'):
# your secured requests here
The private key and the certificate in linux can be generated by the command:
openssl req -new -x509 -days 365 -nodes -out restub.crt -keyout restub.key
The slow connection can be emulated through delay property. It specifies the delay per response in seconds:
from restub import Service
with Service(routes=['GET', r'/$'], delay=0.5) as srv:
# your delayed requests here
Tracing of an output of requests and responses turns on by the setting of trace flag in False:
from restub import Service
with Service(routes=['GET', r'/$'], trace=True) as srv:
# your requests with trace here
At first, we need to create files: index.html and style.css.
Content of index.html:
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="center">Restub test</div>
</body>
</html>
Content of style.css:
body {
background-color: #CCC;
}
.center {
margin: auto;
padding: 10px;
width: 50% ;
border: 3px solid #000;
background: #FFF;
text-align: center;
text-transform: uppercase;
}
Our script:
from restub import Service
service = Service()
service.get(r'/$', '/home/user/../../index.html')
service.get(r'/style.css$', '/home/user/../../style.css')
try:
service.start()
input('Open the page on http://localhost:8081. Press enter for exit...')
except KeyboardInterrupt:
pass
finally:
service.stop()
We specify in routes absolute paths to the files above. Now, run this example, open your browser and go to http://localhost:8081.
import json
import random
import requests
from restub import Service
# Some list of users
users = [
{"id": 0, "name": "John Doe"},
{"id": 1, "name": "Albert Einstein"},
{"id": 2, "name": "Mahatma Gandhi"}
]
routes = [
# return the list of all users
('GET', r'/user/$', json.dumps(users)),
# return the random user
('GET', r'/user/[0-9]+/$', random.choice(users)),
# return status code 204
('PUT', r'/user/$', None, None, 204),
# return custom header
('DELETE', r'/user/[0-9]+/$', None, {'X-HEADER': 'X-VALUE'})
]
# We run a stub on port 7777 and turn on tracing
with Service(routes=routes, port=7777, trace=True) as srv:
requests.get('%s/user/' % srv.host)
requests.get('%s/user/%d/' % (srv.host, 99))
requests.put('%s/user/' % srv.host, json={'name': 'James Bond'})
requests.delete('%s/user/%d/' % (srv.host, 100))
Result of tracing:
[time] Service: 7777 is running at http://localhost:7777
[time] Method GET "/user/", status: 200
Request headers: Response headers:
⚫ Host: localhost: 7777 ⚪ Server: Restub Service
⚫ Accept: */* ⚪ Date: [datetime] GMT
⚫ User-Agent: python-requests ⚪ Content-type: application/json
⚫ Accept-Encoding: gzip, deflate ⚪ Content-length: 106
⚫ Connection: keep-alive
[time] Method GET "/user/99/", status: 200
Request headers: Response headers:
⚫ Host: localhost: 7777 ⚪ Server: Restub Service
⚫ Accept: */* ⚪ Date: [datetime] GMT
⚫ User-Agent: python-requests ⚪ Content-type: application/json
⚫ Accept-Encoding: gzip, deflate ⚪ Content-length: 35
⚫ Connection: keep-alive
[time] Method PUT "/user/", status: 204
Request headers: Response headers:
⚫ Host: localhost: 7777 ⚪ Server: Restub Service
⚫ Accept: */* ⚪ Date: [datetime] GMT
⚫ User-Agent: python-requests
⚫ Accept-Encoding: gzip, deflate
⚫ Connection: keep-alive
⚫ Content-Length: 15
⚫ Content-Type: application/x-www-form-urlencoded
⤇ Payload: b'{"name": "James Bond"}'
[time] Method DELETE "/user/100/", status: 200
Request headers: Response headers:
⚫ Host: localhost: 7777 ⚪ Server: Restub Service
⚫ Accept: */* ⚪ Date: [datetime] GMT
⚫ User-Agent: python-requests ⚪ X-HEADER: X-VALUE
⚫ Accept-Encoding: gzip, deflate
⚫ Connection: keep-alive
⚫ Content-Length: 0
[time] Service: 7777 was stopped
docker-compose up - d
docker exec -it restub tox
This project is licensed under the MIT License - see the LICENSE file for details
Any suggestion and help would be welcome! Get on board! 😏 ✌️