{"id":21813014,"url":"https://github.com/everhide/restub","last_synced_at":"2025-06-11T17:32:35.097Z","repository":{"id":57461598,"uuid":"147603462","full_name":"everhide/restub","owner":"everhide","description":"RESTUB is a library for creating HTTP stubs","archived":false,"fork":false,"pushed_at":"2018-09-06T02:43:13.000Z","size":106,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-29T01:59:02.803Z","etag":null,"topics":["http","mocking","service","stub"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/everhide.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-06T02:01:03.000Z","updated_at":"2024-04-10T08:22:08.000Z","dependencies_parsed_at":"2022-09-26T17:41:00.601Z","dependency_job_id":null,"html_url":"https://github.com/everhide/restub","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/everhide/restub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everhide%2Frestub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everhide%2Frestub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everhide%2Frestub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everhide%2Frestub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/everhide","download_url":"https://codeload.github.com/everhide/restub/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everhide%2Frestub/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259308157,"owners_count":22837974,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","mocking","service","stub"],"created_at":"2024-11-27T14:26:40.619Z","updated_at":"2025-06-11T17:32:35.080Z","avatar_url":"https://github.com/everhide.png","language":"Python","readme":"# RESTUB is a library for creating HTTP stubs\n![](https://img.shields.io/badge/build-passing-brightgreen.svg) ![](https://img.shields.io/badge/coverage-96%25-green.svg)\n\n![](.stub.jpg)\n\n# TL;DR\n\nFor example, creation of the stub returning us Hello World:\n```python\nimport requests\nfrom restub import Service\n\nwith Service(routes=['GET', r'/$', 'Hello world']) as srv:\n    requests.get(srv.host)\n```\n\n# Preamble\n\nOften while developing there is a need to create some HTTP stub.\nFrequent solutions of this problem among developers are:\n- Using shareware and non - free software, like SoapUI\n- Creating service from scratch, for example, using a Flask\n- For unit - tests, using such packages as requests - mock\n\nEach 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.\n\nMain functionality:\n- Automatic addition of necessary headers  according to a contents type\n- Tracing of an output of requests and responses\n- Support of regular expressions in URL\n- Emulation of a slow connection\n- Support of HTTPS\n\n## Routes and routing\n\nA 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.\n\n`method` — an access method, can be \"GET\", \"POST\", \"PUT\" or \"DELETE\"\n\n`path` — describing the response address, can be regex\n\n`* data` — response data, can be str or dict\n\n`* headers` — HTTP response headers\n\n`* status` — code of the response status\n\nWhen 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:\n- 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\n- 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'\n- In all other cases, data will be transferred as 'text/plain'\n\n\n# Running\n\nThe 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.\n\nRun as the a context manager with a change of port:\n```python\nfrom restub import Service\n\nwith Service(routes=['GET', r'/$'], port=7777) as srv:\n    # your requests here\n```\n\nRun as the decorator of function:\n```python\nfrom restub import Service\n\n@Service(routes=['GET', r'/$'])\ndef stubbed_func():\n    # your requests here\n```\nRun as the class instance:\n```python\nfrom restub import Service\n\nsrv = Service(routes=['GET', r'/$'])\nsrv.start()\n# your requests here\nsrv.stop()\n```\n\nRun as the class instance and definition of routes through the functions of the same name:\n```python\nfrom restub import Service\n\nsrv = Service()\nsrv.get(r'/$')  # post(..), put(..), delete(..)\nsrv.start()\n# your requests here\nsrv.stop()\n```\n\nFor work with HTTPS it is necessary to set **secure** flag in True and pass absolute paths to a private key and a certificate:\n```python\nfrom restub import Service\n\nwith Service(routes=['GET', r'/$'], secure=True, crt='\u003cabs path to key\u003e', key='\u003cabs path to cert\u003e'):\n    # your secured requests here\n```\n\nThe private key and the certificate in linux can be generated by the command:\n```shell\nopenssl req -new -x509 -days 365 -nodes -out restub.crt -keyout restub.key\n```\n\nThe slow connection can be emulated through **delay** property. It specifies the delay per response in seconds:\n```python\nfrom restub import Service\n\nwith Service(routes=['GET', r'/$'], delay=0.5) as srv:\n    # your delayed requests here\n```\nTracing of an output of requests and responses turns on by the setting of **trace** flag in False:\n```python\nfrom restub import Service\n\nwith Service(routes=['GET', r'/$'], trace=True) as srv:\n    # your requests with trace here\n```\n# Examples\n\n## Example with the sample web page and css file\nAt first, we need to create files: index.html and style.css.\n\nContent of index.html:\n```html\n\u003chtml\u003e\n\u003chead\u003e\n    \u003clink rel=\"stylesheet\" href=\"style.css\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv class=\"center\"\u003eRestub test\u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\nContent of style.css:\n```css\nbody {\n    background-color:  #CCC;\n}\n\n.center {\n    margin: auto;\n    padding: 10px;\n    width: 50% ;\n    border: 3px solid  #000;\n    background:  #FFF;\n    text-align: center;\n    text-transform: uppercase;\n}\n```\n\nOur script:\n```python\nfrom restub import Service\n\nservice = Service()\nservice.get(r'/$', '/home/user/../../index.html')\nservice.get(r'/style.css$', '/home/user/../../style.css')\ntry:\n    service.start()\n    input('Open the page on http://localhost:8081. Press enter for exit...')\nexcept KeyboardInterrupt:\n    pass\nfinally:\n    service.stop()\n```\n\nWe specify in routes absolute paths to the files above. Now, run this example, open your browser and go to http://localhost:8081.\n\n## Example with a users in the REST style\n```python\nimport json\nimport random\nimport requests\n\nfrom restub import Service\n\n# Some list of users\nusers = [\n    {\"id\": 0, \"name\": \"John Doe\"},\n    {\"id\": 1, \"name\": \"Albert Einstein\"},\n    {\"id\": 2, \"name\": \"Mahatma Gandhi\"}\n]\n\nroutes = [\n    # return the list of all users\n    ('GET', r'/user/$', json.dumps(users)),\n    # return the random user\n    ('GET', r'/user/[0-9]+/$', random.choice(users)),\n    # return status code 204\n    ('PUT', r'/user/$', None, None, 204),\n    # return custom header\n    ('DELETE', r'/user/[0-9]+/$', None, {'X-HEADER': 'X-VALUE'})\n]\n\n# We run a stub on port 7777 and turn on tracing\nwith Service(routes=routes, port=7777, trace=True) as srv:\n    requests.get('%s/user/' % srv.host)\n    requests.get('%s/user/%d/' % (srv.host, 99))\n    requests.put('%s/user/' % srv.host, json={'name': 'James Bond'})\n    requests.delete('%s/user/%d/' % (srv.host, 100))\n```\n\nResult of tracing:\n```\n[time] Service: 7777 is running at http://localhost:7777\n\n[time] Method GET \"/user/\", status: 200\nRequest headers: Response headers:\n⚫ Host: localhost: 7777                      ⚪ Server: Restub Service\n⚫ Accept: */*                                ⚪ Date: [datetime] GMT\n⚫ User-Agent: python-requests                ⚪ Content-type: application/json\n⚫ Accept-Encoding: gzip, deflate             ⚪ Content-length: 106\n⚫ Connection: keep-alive\n\n[time] Method GET \"/user/99/\", status: 200\nRequest headers: Response headers:\n⚫ Host: localhost: 7777                      ⚪ Server: Restub Service\n⚫ Accept: */*                                ⚪ Date: [datetime] GMT\n⚫ User-Agent: python-requests                ⚪ Content-type: application/json\n⚫ Accept-Encoding: gzip, deflate             ⚪ Content-length: 35\n⚫ Connection: keep-alive\n\n[time] Method PUT \"/user/\", status: 204\nRequest headers: Response headers:\n⚫ Host: localhost: 7777                       ⚪ Server: Restub Service\n⚫ Accept: */*                                 ⚪ Date: [datetime] GMT\n⚫ User-Agent: python-requests\n⚫ Accept-Encoding: gzip, deflate\n⚫ Connection: keep-alive\n⚫ Content-Length: 15\n⚫ Content-Type: application/x-www-form-urlencoded\n⤇ Payload: b'{\"name\": \"James Bond\"}'\n\n[time] Method DELETE \"/user/100/\", status: 200\nRequest headers: Response headers:\n⚫ Host: localhost: 7777                     ⚪ Server: Restub Service\n⚫ Accept: */*                               ⚪ Date: [datetime] GMT\n⚫ User-Agent: python-requests               ⚪ X-HEADER: X-VALUE\n⚫ Accept-Encoding: gzip, deflate\n⚫ Connection: keep-alive\n⚫ Content-Length: 0\n\n[time] Service: 7777 was stopped\n```\n\n# Running the tests\n```shell\ndocker-compose  up - d\ndocker exec -it restub tox\n```\n\n# License\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details\n\n# Join\nAny suggestion and help would be welcome! Get on board! :smirk: :v:\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feverhide%2Frestub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feverhide%2Frestub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feverhide%2Frestub/lists"}