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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mimicker
3
- Version: 2.0.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.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 = "^6.0"
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 = "<3.0"
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
@@ -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