mimicker 2.0.0__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mimicker-2.0.0 → mimicker-2.0.2}/PKG-INFO +3 -3
- {mimicker-2.0.0 → mimicker-2.0.2}/README.md +2 -2
- {mimicker-2.0.0 → mimicker-2.0.2}/mimicker/handler.py +3 -0
- mimicker-2.0.2/mimicker/mimicker.py +85 -0
- mimicker-2.0.2/mimicker/route.py +113 -0
- {mimicker-2.0.0 → mimicker-2.0.2}/mimicker/server.py +31 -0
- {mimicker-2.0.0 → mimicker-2.0.2}/pyproject.toml +3 -3
- mimicker-2.0.0/mimicker/mimicker.py +0 -27
- mimicker-2.0.0/mimicker/route.py +0 -54
- {mimicker-2.0.0 → mimicker-2.0.2}/LICENSE +0 -0
- {mimicker-2.0.0 → mimicker-2.0.2}/mimicker/__init__.py +0 -0
- {mimicker-2.0.0 → mimicker-2.0.2}/mimicker/stub_group.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mimicker
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: A lightweight HTTP mocking server for Python
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: http,mocking,testing,mock-server,stubbing,ci-cd,http-server,testing-tools,stub-server,purepython
|
|
@@ -248,6 +248,7 @@ mimicker(8080).routes(
|
|
|
248
248
|
* `post(path)`: Defines a `POST` endpoint.
|
|
249
249
|
* `put(path)`: Defines a `PUT` endpoint.
|
|
250
250
|
* `delete(path)`: Defines a `DELETE` endpoint.
|
|
251
|
+
* `patch(path)`: Defines a `PATCH` endpoint.
|
|
251
252
|
* `.delay(duration)`: Defines the delay in seconds waited before returning the response (optional, 0. by default).
|
|
252
253
|
* `.body(content)`: Defines the response `body`.
|
|
253
254
|
* `.status(code)`: Defines the response `status code`.
|
|
@@ -270,10 +271,9 @@ or 🗣️ discuss features and ideas @ [slack community](https://join.slack.com
|
|
|
270
271
|
I'm thankful to all the people who have contributed to this project.
|
|
271
272
|
|
|
272
273
|
<a href="https://github.com/amaziahub/mimicker/graphs/contributors">
|
|
273
|
-
<img src="https://contrib.rocks/image?repo=amaziahub/mimicker" />
|
|
274
|
+
<img src="https://contrib.rocks/image?repo=amaziahub/mimicker" />
|
|
274
275
|
</a>
|
|
275
276
|
|
|
276
277
|
|
|
277
|
-
|
|
278
278
|
## License
|
|
279
279
|
Mimicker is released under the MIT License. see the [LICENSE](LICENSE) for more information.
|
|
@@ -226,6 +226,7 @@ mimicker(8080).routes(
|
|
|
226
226
|
* `post(path)`: Defines a `POST` endpoint.
|
|
227
227
|
* `put(path)`: Defines a `PUT` endpoint.
|
|
228
228
|
* `delete(path)`: Defines a `DELETE` endpoint.
|
|
229
|
+
* `patch(path)`: Defines a `PATCH` endpoint.
|
|
229
230
|
* `.delay(duration)`: Defines the delay in seconds waited before returning the response (optional, 0. by default).
|
|
230
231
|
* `.body(content)`: Defines the response `body`.
|
|
231
232
|
* `.status(code)`: Defines the response `status code`.
|
|
@@ -248,10 +249,9 @@ or 🗣️ discuss features and ideas @ [slack community](https://join.slack.com
|
|
|
248
249
|
I'm thankful to all the people who have contributed to this project.
|
|
249
250
|
|
|
250
251
|
<a href="https://github.com/amaziahub/mimicker/graphs/contributors">
|
|
251
|
-
<img src="https://contrib.rocks/image?repo=amaziahub/mimicker" />
|
|
252
|
+
<img src="https://contrib.rocks/image?repo=amaziahub/mimicker" />
|
|
252
253
|
</a>
|
|
253
254
|
|
|
254
255
|
|
|
255
|
-
|
|
256
256
|
## License
|
|
257
257
|
Mimicker is released under the MIT License. see the [LICENSE](LICENSE) for more information.
|
|
@@ -23,6 +23,9 @@ class MimickerHandler(http.server.SimpleHTTPRequestHandler):
|
|
|
23
23
|
def do_DELETE(self):
|
|
24
24
|
self._handle_request("DELETE")
|
|
25
25
|
|
|
26
|
+
def do_PATCH(self):
|
|
27
|
+
self._handle_request("PATCH")
|
|
28
|
+
|
|
26
29
|
def _handle_request(self, method: str):
|
|
27
30
|
request_headers = {key.lower(): value for key, value in self.headers.items()}
|
|
28
31
|
matched_stub, path_params = self.stub_matcher.match(
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from mimicker.route import Route
|
|
4
|
+
from mimicker.server import MimickerServer
|
|
5
|
+
|
|
6
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get(path: str) -> Route:
|
|
10
|
+
"""
|
|
11
|
+
Creates a GET route for the specified path.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
path (str): The URL path for the route.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Route: A GET route instance.
|
|
18
|
+
"""
|
|
19
|
+
return Route("GET", path)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def post(path: str) -> Route:
|
|
23
|
+
"""
|
|
24
|
+
Creates a POST route for the specified path.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
path (str): The URL path for the route.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Route: A POST route instance.
|
|
31
|
+
"""
|
|
32
|
+
return Route("POST", path)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def put(path: str) -> Route:
|
|
36
|
+
"""
|
|
37
|
+
Creates a PUT route for the specified path.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
path (str): The URL path for the route.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Route: A PUT route instance.
|
|
44
|
+
"""
|
|
45
|
+
return Route("PUT", path)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def delete(path: str) -> Route:
|
|
49
|
+
"""
|
|
50
|
+
Creates a DELETE route for the specified path.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
path (str): The URL path for the route.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Route: A DELETE route instance.
|
|
57
|
+
"""
|
|
58
|
+
return Route("DELETE", path)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def patch(path: str) -> Route:
|
|
62
|
+
"""
|
|
63
|
+
Creates a PATCH route for the specified path.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
path (str): The URL path for the route.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Route: A PATCH route instance.
|
|
70
|
+
"""
|
|
71
|
+
return Route("PATCH", path)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def mimicker(port: int = 8080) -> MimickerServer:
|
|
75
|
+
"""
|
|
76
|
+
Starts a Mimicker server on the specified port.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
port (int, optional): The port to run the server on. Defaults to 8080.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
MimickerServer: An instance of the running Mimicker server.
|
|
83
|
+
"""
|
|
84
|
+
server = MimickerServer(port).start()
|
|
85
|
+
return server
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, Tuple, Any, Callable, Optional, Pattern, Union, List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Route:
|
|
6
|
+
"""
|
|
7
|
+
Represents an HTTP route with configurable response properties.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, method: str, path: str):
|
|
11
|
+
"""
|
|
12
|
+
Initializes a new Route.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
method (str): The HTTP method (GET, POST, PUT, DELETE, etc.).
|
|
16
|
+
path (str): The URL path for the route, supporting parameterized paths.
|
|
17
|
+
"""
|
|
18
|
+
self.method = method
|
|
19
|
+
self.path = path
|
|
20
|
+
self._body = {}
|
|
21
|
+
self._delay = 0.
|
|
22
|
+
self._status = 200
|
|
23
|
+
self._headers: List[Tuple[str, str]] = []
|
|
24
|
+
self._response_func: Optional[Callable[[], Tuple[int, Any]]] = None
|
|
25
|
+
|
|
26
|
+
escaped_path = re.escape(path)
|
|
27
|
+
parameterized_path = re.sub(r'\\{(\w+)\\}',
|
|
28
|
+
r'(?P<\1>[^/]+)', escaped_path)
|
|
29
|
+
self._compiled_path: Pattern = re.compile(f"^{parameterized_path}$")
|
|
30
|
+
|
|
31
|
+
def delay(self, delay: float):
|
|
32
|
+
"""
|
|
33
|
+
Sets the delay (in seconds) before returning the response.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
delay (float): The delay time in seconds.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Route: The current Route instance (for method chaining).
|
|
40
|
+
"""
|
|
41
|
+
self._delay = delay
|
|
42
|
+
return self
|
|
43
|
+
|
|
44
|
+
def body(self, response: Union[Dict[str, Any], str] = None):
|
|
45
|
+
"""
|
|
46
|
+
Sets the response body for the route.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
response (Union[Dict[str, Any], str], optional): The response body (JSON or string).
|
|
50
|
+
Defaults to an empty string.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Route: The current Route instance (for method chaining).
|
|
54
|
+
"""
|
|
55
|
+
self._body = response if response is not None else ""
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def status(self, status_code: int):
|
|
59
|
+
"""
|
|
60
|
+
Sets the HTTP status code for the response.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
status_code (int): The HTTP status code (e.g., 200, 404, 500).
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Route: The current Route instance (for method chaining).
|
|
67
|
+
"""
|
|
68
|
+
self._status = status_code
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def headers(self, headers: List[Tuple[str, str]]):
|
|
72
|
+
"""
|
|
73
|
+
Sets the HTTP headers for the response.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
headers (List[Tuple[str, str]]): A list of key-value pairs representing headers.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Route: The current Route instance (for method chaining).
|
|
80
|
+
"""
|
|
81
|
+
self._headers = headers
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def response_func(self, func: Callable[[], Tuple[int, Any]]):
|
|
85
|
+
"""
|
|
86
|
+
Sets a custom response function for dynamic responses.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
func (Callable[[], Tuple[int, Any]]): A function returning a tuple (status_code, response_body).
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Route: The current Route instance (for method chaining).
|
|
93
|
+
"""
|
|
94
|
+
self._response_func = func
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def build(self):
|
|
98
|
+
"""
|
|
99
|
+
Builds the route configuration dictionary.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dict[str, Any]: The route configuration containing method, path, response settings, and handlers.
|
|
103
|
+
"""
|
|
104
|
+
return {
|
|
105
|
+
"method": self.method,
|
|
106
|
+
"path": self.path,
|
|
107
|
+
"compiled_path": self._compiled_path,
|
|
108
|
+
"delay": self._delay,
|
|
109
|
+
"body": self._body,
|
|
110
|
+
"status": self._status,
|
|
111
|
+
"headers": self._headers,
|
|
112
|
+
"response_func": self._response_func
|
|
113
|
+
}
|
|
@@ -13,7 +13,18 @@ class ReusableAddressThreadingTCPServer(socketserver.ThreadingTCPServer):
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MimickerServer:
|
|
16
|
+
"""
|
|
17
|
+
A lightweight HTTP mocking server.
|
|
18
|
+
|
|
19
|
+
This server allows defining request-response routes for testing or simulation purposes.
|
|
20
|
+
"""
|
|
16
21
|
def __init__(self, port: int = 8080):
|
|
22
|
+
"""
|
|
23
|
+
Initializes the Mimicker server.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
port (int, optional): The port to run the server on. Defaults to 8080.
|
|
27
|
+
"""
|
|
17
28
|
self.stub_matcher = StubGroup()
|
|
18
29
|
self.server = ReusableAddressThreadingTCPServer(("", port), self._handler_factory)
|
|
19
30
|
self._thread = threading.Thread(target=self.server.serve_forever, daemon=True)
|
|
@@ -23,6 +34,15 @@ class MimickerServer:
|
|
|
23
34
|
return MimickerHandler(self.stub_matcher, *args)
|
|
24
35
|
|
|
25
36
|
def routes(self, *routes: Route):
|
|
37
|
+
"""
|
|
38
|
+
Adds multiple routes to the server.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
*routes (Route): One or more Route instances to be added.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
MimickerServer: The current server instance (for method chaining).
|
|
45
|
+
"""
|
|
26
46
|
for route in routes:
|
|
27
47
|
route_config = route.build()
|
|
28
48
|
self.stub_matcher.add(
|
|
@@ -37,12 +57,23 @@ class MimickerServer:
|
|
|
37
57
|
return self
|
|
38
58
|
|
|
39
59
|
def start(self):
|
|
60
|
+
"""
|
|
61
|
+
Starts the Mimicker server in a background thread.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
MimickerServer: The current server instance (for method chaining).
|
|
65
|
+
"""
|
|
40
66
|
logging.info("MimickerServer starting on port %s",
|
|
41
67
|
self.server.server_address[1])
|
|
42
68
|
self._thread.start()
|
|
43
69
|
return self
|
|
44
70
|
|
|
45
71
|
def shutdown(self):
|
|
72
|
+
"""
|
|
73
|
+
Shuts down the Mimicker server gracefully.
|
|
74
|
+
|
|
75
|
+
Ensures that the server is stopped and the thread is joined if still running.
|
|
76
|
+
"""
|
|
46
77
|
self.server.server_close()
|
|
47
78
|
self.server.shutdown()
|
|
48
79
|
if self._thread.is_alive():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "mimicker"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.2"
|
|
4
4
|
description = "A lightweight HTTP mocking server for Python"
|
|
5
5
|
authors = ["Amazia Gur <amaziagur@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -13,12 +13,12 @@ homepage = "https://github.com/amaziahub/mimicker"
|
|
|
13
13
|
python = "^3.7"
|
|
14
14
|
|
|
15
15
|
[tool.poetry.dev-dependencies]
|
|
16
|
-
pytest = "^
|
|
16
|
+
pytest = "^7.4"
|
|
17
17
|
PyHamcrest = "^2.0"
|
|
18
18
|
requests = "^2.0"
|
|
19
19
|
|
|
20
20
|
[tool.poetry.group.dev.dependencies]
|
|
21
|
-
pytest-cov = "<
|
|
21
|
+
pytest-cov = "<5.0"
|
|
22
22
|
|
|
23
23
|
[build-system]
|
|
24
24
|
requires = ["poetry-core>=1.0.0"]
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
from mimicker.route import Route
|
|
4
|
-
from mimicker.server import MimickerServer
|
|
5
|
-
|
|
6
|
-
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get(path: str) -> Route:
|
|
10
|
-
return Route("GET", path)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def post(path: str) -> Route:
|
|
14
|
-
return Route("POST", path)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def put(path: str) -> Route:
|
|
18
|
-
return Route("PUT", path)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def delete(path: str) -> Route:
|
|
22
|
-
return Route("DELETE", path)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def mimicker(port: int = 8080) -> MimickerServer:
|
|
26
|
-
server = MimickerServer(port).start()
|
|
27
|
-
return server
|
mimicker-2.0.0/mimicker/route.py
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from typing import Dict, Tuple, Any, Callable, Optional, Pattern, Union, List
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Route:
|
|
7
|
-
def __init__(self, method: str, path: str):
|
|
8
|
-
self.method = method
|
|
9
|
-
self.path = path
|
|
10
|
-
self._body = {}
|
|
11
|
-
self._delay = 0.
|
|
12
|
-
self._status = 200
|
|
13
|
-
self._headers: List[Tuple[str, str]] = []
|
|
14
|
-
self._response_func: Optional[Callable[[], Tuple[int, Any]]] = None
|
|
15
|
-
|
|
16
|
-
escaped_path = re.escape(path)
|
|
17
|
-
parameterized_path = re.sub(r'\\{(\w+)\\}',
|
|
18
|
-
r'(?P<\1>[^/]+)', escaped_path)
|
|
19
|
-
self._compiled_path: Pattern = re.compile(f"^{parameterized_path}$")
|
|
20
|
-
|
|
21
|
-
def delay(self, delay: float):
|
|
22
|
-
"""
|
|
23
|
-
Sets the delay (in seconds) to wait before returning the response
|
|
24
|
-
"""
|
|
25
|
-
self._delay = delay
|
|
26
|
-
return self
|
|
27
|
-
|
|
28
|
-
def body(self, response: Union[Dict[str, Any], str] = None):
|
|
29
|
-
self._body = response if response is not None else ""
|
|
30
|
-
return self
|
|
31
|
-
|
|
32
|
-
def status(self, status_code: int):
|
|
33
|
-
self._status = status_code
|
|
34
|
-
return self
|
|
35
|
-
|
|
36
|
-
def headers(self, headers: List[Tuple[str, str]]):
|
|
37
|
-
self._headers = headers
|
|
38
|
-
return self
|
|
39
|
-
|
|
40
|
-
def response_func(self, func: Callable[[], Tuple[int, Any]]):
|
|
41
|
-
self._response_func = func
|
|
42
|
-
return self
|
|
43
|
-
|
|
44
|
-
def build(self):
|
|
45
|
-
return {
|
|
46
|
-
"method": self.method,
|
|
47
|
-
"path": self.path,
|
|
48
|
-
"compiled_path": self._compiled_path,
|
|
49
|
-
"delay": self._delay,
|
|
50
|
-
"body": self._body,
|
|
51
|
-
"status": self._status,
|
|
52
|
-
"headers": self._headers,
|
|
53
|
-
"response_func": self._response_func
|
|
54
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|