proxy-mock 2.10.1__py3-none-any.whl

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.
Files changed (42) hide show
  1. proxy_mock/__init__.py +10 -0
  2. proxy_mock/any_catcher.py +8 -0
  3. proxy_mock/api/__init__.py +0 -0
  4. proxy_mock/api/deps.py +5 -0
  5. proxy_mock/api/routes/__init__.py +0 -0
  6. proxy_mock/api/routes/configure.py +50 -0
  7. proxy_mock/api/routes/service.py +24 -0
  8. proxy_mock/api/routes/storage.py +51 -0
  9. proxy_mock/api/routes/traffic.py +79 -0
  10. proxy_mock/api/schemas.py +10 -0
  11. proxy_mock/app/__init__.py +3 -0
  12. proxy_mock/app/factory.py +97 -0
  13. proxy_mock/app/state.py +0 -0
  14. proxy_mock/client/__init__.py +4 -0
  15. proxy_mock/client/async_client.py +237 -0
  16. proxy_mock/client/client.py +174 -0
  17. proxy_mock/client/route.py +93 -0
  18. proxy_mock/client/service_endpoints.py +14 -0
  19. proxy_mock/core/__init__.py +0 -0
  20. proxy_mock/core/logging.py +16 -0
  21. proxy_mock/core/serializers.py +32 -0
  22. proxy_mock/core/settings.py +50 -0
  23. proxy_mock/core/time.py +6 -0
  24. proxy_mock/domain/__init__.py +20 -0
  25. proxy_mock/domain/constants.py +16 -0
  26. proxy_mock/domain/models.py +94 -0
  27. proxy_mock/repositories/__init__.py +0 -0
  28. proxy_mock/repositories/mock_storage.py +76 -0
  29. proxy_mock/repositories/traffic_store.py +69 -0
  30. proxy_mock/services/__init__.py +0 -0
  31. proxy_mock/services/cache_service.py +46 -0
  32. proxy_mock/services/mock_service.py +102 -0
  33. proxy_mock/services/proxy_service.py +104 -0
  34. proxy_mock/services/request_parser.py +30 -0
  35. proxy_mock/services/response_factory.py +25 -0
  36. proxy_mock/services/rule_engine.py +84 -0
  37. proxy_mock/services/traffic_service.py +26 -0
  38. proxy_mock/utils.py +145 -0
  39. proxy_mock-2.10.1.dist-info/METADATA +352 -0
  40. proxy_mock-2.10.1.dist-info/RECORD +42 -0
  41. proxy_mock-2.10.1.dist-info/WHEEL +4 -0
  42. proxy_mock-2.10.1.dist-info/licenses/LICENSE +21 -0
proxy_mock/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from importlib import metadata
2
+
3
+ from proxy_mock.app import create_app
4
+
5
+ try:
6
+ __version__ = metadata.version(__package__)
7
+ except ModuleNotFoundError:
8
+ __version__ = ""
9
+
10
+ __all__ = ["create_app"]
@@ -0,0 +1,8 @@
1
+ from proxy_mock.app import create_app
2
+
3
+ app = create_app()
4
+
5
+ if __name__ == "__main__":
6
+ import uvicorn
7
+
8
+ uvicorn.run("any_catcher:app", port=5000, log_level="info", reload=True)
File without changes
proxy_mock/api/deps.py ADDED
@@ -0,0 +1,5 @@
1
+ from fastapi import Request
2
+
3
+
4
+ def get_app(request: Request):
5
+ return request.app
File without changes
@@ -0,0 +1,50 @@
1
+ import json
2
+ from copy import deepcopy
3
+
4
+ from fastapi import APIRouter, Request
5
+ from fastapi.responses import JSONResponse
6
+ from pydantic import ValidationError
7
+
8
+ from proxy_mock.domain.constants import ParseError
9
+ from proxy_mock.domain.models import ConfigureMockRequestSchema
10
+ from proxy_mock.services.mock_service import mock_initialization, patch_mock_data, return_mock_data
11
+ from proxy_mock.services.request_parser import parse_configure
12
+
13
+ router = APIRouter()
14
+
15
+
16
+ @router.post("/configure_mock")
17
+ async def configure_mock(request: Request):
18
+ try:
19
+ parsed_data = await parse_configure(request)
20
+ except ParseError as err:
21
+ return JSONResponse({"success": False, "error": err.detail}, err.code)
22
+
23
+ try:
24
+ validate_data = ConfigureMockRequestSchema.model_validate(parsed_data).model_dump()
25
+ except ValidationError as err:
26
+ return JSONResponse({"success": False, "error": json.loads(err.json())}, 422)
27
+
28
+ response = await mock_initialization(request.app, validate_data)
29
+ return JSONResponse(response, 201)
30
+
31
+
32
+ @router.patch("/configure_mock")
33
+ async def patch_mock_configuration(request: Request):
34
+ try:
35
+ parsed_data = await parse_configure(request)
36
+ except ParseError as err:
37
+ return JSONResponse({"success": False, "error": err.detail}, err.code)
38
+
39
+ try:
40
+ validate_data = ConfigureMockRequestSchema.model_validate(parsed_data).model_dump(exclude_unset=True)
41
+ except ValidationError as err:
42
+ return JSONResponse({"success": False, "error": json.loads(err.json())}, 422)
43
+
44
+ saved_data = await return_mock_data(validate_data["path"])
45
+ if not saved_data:
46
+ return JSONResponse({"success": False, "error": f"There is no mock for {validate_data['path']}"}, 400)
47
+
48
+ mock_data = deepcopy(await patch_mock_data(saved_data, validate_data))
49
+ response = await mock_initialization(request.app, mock_data)
50
+ return JSONResponse(response, 200)
@@ -0,0 +1,24 @@
1
+ import platform
2
+
3
+ from fastapi import APIRouter, Request
4
+ from fastapi.responses import JSONResponse
5
+
6
+ from proxy_mock.services.mock_service import count_mocks
7
+
8
+ router = APIRouter()
9
+
10
+
11
+ @router.get("/proxy_mock")
12
+ async def get_proxy_mock(request: Request):
13
+ app = request.app
14
+ return JSONResponse(
15
+ {
16
+ "success": True,
17
+ "version": app.state.version,
18
+ "python_version": platform.python_version(),
19
+ "mocks_count": await count_mocks(),
20
+ "traffic_count": await app.state.traffic_store.count(),
21
+ "traffic_max_items": app.state.traffic_store.max_items,
22
+ },
23
+ 200,
24
+ )
@@ -0,0 +1,51 @@
1
+ from fastapi import APIRouter, Request
2
+ from fastapi.responses import JSONResponse
3
+
4
+ from proxy_mock.core.serializers import convert_bytes_to_str
5
+ from proxy_mock.services.mock_service import (
6
+ cleanup_storage,
7
+ delete_mock_data,
8
+ remove_runtime_routes,
9
+ return_mock_data,
10
+ return_storage,
11
+ )
12
+
13
+ router = APIRouter()
14
+
15
+
16
+ @router.get("/storage")
17
+ async def get_storage(request: Request):
18
+ path = request.query_params.get("path")
19
+ data = await return_mock_data(path) if path else await return_storage()
20
+ return JSONResponse({"success": True, "data": convert_bytes_to_str(data)}, 200)
21
+
22
+
23
+ @router.delete("/storage")
24
+ async def delete_storage(request: Request):
25
+ """Delete mocks. Without `path` all of them; with `path` only that one (`404` if missing)."""
26
+ path = request.query_params.get("path")
27
+
28
+ if not path:
29
+ result = await cleanup_storage(request.app)
30
+ return JSONResponse({"success": result, "data": convert_bytes_to_str(await return_storage())}, 200)
31
+
32
+ remove_runtime_routes(request.app, path)
33
+ deleted = await delete_mock_data(path)
34
+ if not deleted:
35
+ return JSONResponse({"success": False, "error": f"No mock found for {path}"}, 404)
36
+
37
+ return JSONResponse({"success": True, "data": convert_bytes_to_str(await return_storage())}, 200)
38
+
39
+
40
+ @router.post("/storage/clean")
41
+ async def clean_storage(request: Request):
42
+ """Deprecated alias for deleting mocks. The RESTful variant is `DELETE /storage`."""
43
+ path = request.query_params.get("path")
44
+
45
+ if path:
46
+ remove_runtime_routes(request.app, path)
47
+ result = await delete_mock_data(path)
48
+ else:
49
+ result = await cleanup_storage(request.app)
50
+
51
+ return JSONResponse({"success": result, "data": convert_bytes_to_str(await return_storage())}, 200)
@@ -0,0 +1,79 @@
1
+ import json
2
+
3
+ from aiocache import Cache
4
+ from fastapi import APIRouter, Request
5
+ from fastapi.responses import JSONResponse
6
+ from pydantic import ValidationError
7
+
8
+ from proxy_mock.domain.models import TrafficSettingsSchema
9
+
10
+ router = APIRouter()
11
+
12
+
13
+ def _traffic_settings(request: Request) -> dict:
14
+ return {
15
+ "record_unknown_traffic": request.app.state.record_unknown_traffic,
16
+ "max_items": request.app.state.traffic_store.max_items,
17
+ }
18
+
19
+
20
+ @router.get("/traffic")
21
+ async def get_traffic(request: Request):
22
+ path = request.query_params.get("path")
23
+ method = request.query_params.get("method")
24
+ limit_raw = request.query_params.get("limit")
25
+ limit = int(limit_raw) if limit_raw and limit_raw.isdigit() else None
26
+
27
+ data = await request.app.state.traffic_store.list(path=path, method=method, limit=limit)
28
+ return JSONResponse({"success": True, "count": len(data), "data": data}, 200)
29
+
30
+
31
+ @router.post("/traffic/clean")
32
+ async def clean_traffic(request: Request):
33
+ await request.app.state.traffic_store.clear()
34
+ return JSONResponse({"success": True, "data": []}, 200)
35
+
36
+
37
+ @router.get("/traffic/settings")
38
+ async def get_traffic_settings(request: Request):
39
+ return JSONResponse({"success": True, "data": _traffic_settings(request)}, 200)
40
+
41
+
42
+ @router.post("/traffic/settings")
43
+ async def set_traffic_settings(request: Request):
44
+ """Traffic recording settings, changeable without restarting the service.
45
+
46
+ The update is partial: only the supplied fields are applied.
47
+ """
48
+ try:
49
+ payload = await request.json()
50
+ except ValueError:
51
+ return JSONResponse({"success": False, "error": "The request body must be valid JSON"}, 400)
52
+
53
+ try:
54
+ settings = TrafficSettingsSchema.model_validate(payload)
55
+ except ValidationError as err:
56
+ return JSONResponse({"success": False, "error": json.loads(err.json())}, 422)
57
+
58
+ changes = settings.model_dump(exclude_unset=True, exclude_none=True)
59
+ if not changes:
60
+ return JSONResponse(
61
+ {"success": False, "error": "Provide at least one field: record_unknown_traffic, max_items"}, 422
62
+ )
63
+
64
+ if "record_unknown_traffic" in changes:
65
+ request.app.state.record_unknown_traffic = changes["record_unknown_traffic"]
66
+
67
+ if "max_items" in changes:
68
+ await request.app.state.traffic_store.set_max_items(changes["max_items"])
69
+
70
+ request.app.logger.info(f"Traffic settings updated: {changes}")
71
+
72
+ return JSONResponse({"success": True, "data": _traffic_settings(request)}, 200)
73
+
74
+
75
+ @router.post("/cache/clean")
76
+ async def clean_cache(request: Request):
77
+ await request.app.state.cache.close()
78
+ request.app.state.cache = Cache(Cache.MEMORY, namespace="mocks")
79
+ return JSONResponse({"success": True}, 200)
@@ -0,0 +1,10 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class SuccessResponse(BaseModel):
5
+ success: bool = True
6
+
7
+
8
+ class ErrorResponse(BaseModel):
9
+ success: bool = False
10
+ error: str | dict | list
@@ -0,0 +1,3 @@
1
+ from proxy_mock.app.factory import create_app
2
+
3
+ __all__ = ["create_app"]
@@ -0,0 +1,97 @@
1
+ import os
2
+ from contextlib import asynccontextmanager
3
+
4
+ import httpx2
5
+ from aiocache import Cache
6
+ from fastapi import FastAPI, Request
7
+ from fastapi.responses import JSONResponse
8
+
9
+ from proxy_mock.api.routes.configure import router as configure_router
10
+ from proxy_mock.api.routes.service import router as service_router
11
+ from proxy_mock.api.routes.storage import router as storage_router
12
+ from proxy_mock.api.routes.traffic import router as traffic_router
13
+ from proxy_mock.core.logging import app_logger
14
+ from proxy_mock.core.settings import get_version_from_pyproject, record_unknown_traffic_default
15
+ from proxy_mock.repositories.traffic_store import TrafficStore
16
+ from proxy_mock.services.traffic_service import new_traffic_data
17
+ from proxy_mock.utils import log_request
18
+
19
+
20
+ def _proxy_timeout() -> float:
21
+ raw = os.getenv("PROXY_MOCK_PROXY_TIMEOUT", "").strip()
22
+ try:
23
+ value = float(raw)
24
+ return value if value > 0 else 30.0
25
+ except ValueError:
26
+ return 30.0
27
+
28
+
29
+ @asynccontextmanager
30
+ async def app_lifespan(app: FastAPI):
31
+ app.state.http_client = httpx2.AsyncClient(timeout=httpx2.Timeout(_proxy_timeout()))
32
+ try:
33
+ yield
34
+ finally:
35
+ await app.state.http_client.aclose()
36
+
37
+
38
+ class ProxyMockApp(FastAPI):
39
+ def __init__(self, *args, **kwargs):
40
+ kwargs.setdefault("lifespan", app_lifespan)
41
+ super().__init__(*args, **kwargs)
42
+
43
+ self.state.cache = Cache(Cache.MEMORY, namespace="mocks")
44
+ self.state.traffic_store = TrafficStore()
45
+ # Toggled at runtime via POST /traffic/settings, so it lives in state rather than in a module constant.
46
+ self.state.record_unknown_traffic = record_unknown_traffic_default()
47
+ self.logger = app_logger
48
+
49
+
50
+ def create_app() -> ProxyMockApp:
51
+ app = ProxyMockApp(title="Proxy Mock")
52
+ app.state.version = get_version_from_pyproject(app.logger)
53
+
54
+ app.include_router(service_router)
55
+ app.include_router(configure_router)
56
+ app.include_router(storage_router)
57
+ app.include_router(traffic_router)
58
+
59
+ @app.middleware("http")
60
+ @log_request
61
+ async def request_middleware(request: Request, call_next):
62
+ return await call_next(request)
63
+
64
+ @app.exception_handler(404)
65
+ async def custom_404_handler(request: Request, exc):
66
+ app.logger.warning(f"No mock found for {request.url.path}")
67
+ if app.state.record_unknown_traffic:
68
+ try:
69
+ await new_traffic_data(app, request, {"status_code": 404})
70
+ except Exception as err: # recording traffic must not break the 404 response
71
+ app.logger.error(f"Failed to record traffic for the 404: {err}")
72
+ return JSONResponse({"error": f"No mock found for {request.url.path}"}, 404)
73
+
74
+ app.logger.info(f"Service routes: {sorted(collect_service_paths(app))}")
75
+
76
+ return app
77
+
78
+
79
+ def collect_service_paths(app: FastAPI) -> set[str]:
80
+ """Paths of the service endpoints.
81
+
82
+ Since the FastAPI upgrade, routes added by include_router are nested in _IncludedRouter
83
+ and have no top-level ``path`` attribute of their own, so we read them from original_router.
84
+ """
85
+ paths: set[str] = set()
86
+ for route in app.routes:
87
+ original_router = getattr(route, "original_router", None)
88
+ if original_router is not None:
89
+ for inner in getattr(original_router, "routes", []):
90
+ inner_path = getattr(inner, "path", None)
91
+ if inner_path:
92
+ paths.add(inner_path)
93
+ else:
94
+ route_path = getattr(route, "path", None)
95
+ if route_path:
96
+ paths.add(route_path)
97
+ return paths
File without changes
@@ -0,0 +1,4 @@
1
+ from proxy_mock.client.async_client import AsyncProxyMock
2
+ from proxy_mock.client.client import ProxyMock
3
+
4
+ __all__ = ["ProxyMock", "AsyncProxyMock"]
@@ -0,0 +1,237 @@
1
+ from http import HTTPMethod
2
+ from typing import Any
3
+
4
+ import httpx2
5
+ import msgpack
6
+ from yarl import URL
7
+
8
+ from proxy_mock.client.service_endpoints import Endpoints
9
+
10
+ CONFIGURE_CONTENT_TYPE = "application/octet-stream"
11
+
12
+
13
+ def _build_traffic_settings_payload(record_unknown_traffic: bool | None, max_items: int | None) -> dict:
14
+ """The endpoint accepts a partial update, so fields that were not supplied are left out."""
15
+ payload = {}
16
+ if record_unknown_traffic is not None:
17
+ payload["record_unknown_traffic"] = record_unknown_traffic
18
+ if max_items is not None:
19
+ payload["max_items"] = max_items
20
+ return payload
21
+
22
+
23
+ class AsyncProxyMockRequestError(Exception):
24
+ """Error while performing an async request to the proxy-mock server."""
25
+
26
+
27
+ class AsyncProxyMockResponseError(Exception):
28
+ """HTTP error returned by the proxy-mock server."""
29
+
30
+
31
+ class AsyncProxyMock:
32
+ def __init__(self, host: str, timeout: float = 10.0) -> None:
33
+ self.host = host.rstrip("/")
34
+ self.timeout = timeout
35
+ self._client = httpx2.AsyncClient(base_url=self.host, timeout=timeout)
36
+
37
+ async def aclose(self) -> None:
38
+ await self._client.aclose()
39
+
40
+ async def __aenter__(self):
41
+ return self
42
+
43
+ async def __aexit__(self, exc_type, exc, tb):
44
+ await self.aclose()
45
+
46
+ @staticmethod
47
+ def _parse_response_body(response: httpx2.Response) -> dict | str | list | bytes | None:
48
+ if not response.content:
49
+ return None
50
+
51
+ content_type = response.headers.get("Content-Type", "")
52
+ if "json" in content_type:
53
+ try:
54
+ return response.json()
55
+ except Exception:
56
+ return response.text
57
+ if "text" in content_type:
58
+ return response.text
59
+ return response.content
60
+
61
+ async def execute_request(
62
+ self,
63
+ method: str | HTTPMethod,
64
+ route: str,
65
+ raise_for_status: bool = False,
66
+ **kwargs,
67
+ ) -> httpx2.Response:
68
+ try:
69
+ response = await self._client.request(method=str(method), url=route, **kwargs)
70
+ except (httpx2.ConnectError, httpx2.TimeoutException) as err:
71
+ raise AsyncProxyMockRequestError(f"Proxy-mock request error:\n{err}") from err
72
+
73
+ if raise_for_status and response.status_code >= 400:
74
+ raise AsyncProxyMockResponseError(f"Proxy-mock returned HTTP {response.status_code}: {response.text}")
75
+ return response
76
+
77
+ async def execute_request_and_get_response_body(
78
+ self,
79
+ method: str | HTTPMethod,
80
+ route: str,
81
+ json: dict | None = None,
82
+ **kwargs,
83
+ ) -> dict | str | list | bytes | None:
84
+ response = await self.execute_request(method=method, route=route, json=json, **kwargs)
85
+ return self._parse_response_body(response)
86
+
87
+ def _build_mock_request_data(
88
+ self,
89
+ path: str,
90
+ body: Any = None,
91
+ headers: dict | None = None,
92
+ status_code: int | None = None,
93
+ extra_info: dict | None = None,
94
+ proxy_host: str | None = None,
95
+ timeout: float | None = None,
96
+ rules: list[dict] | None = None,
97
+ methods: list[str] | None = None,
98
+ cache_time: int | None = None,
99
+ include_body_if_none: bool = True,
100
+ **kwargs,
101
+ ) -> dict:
102
+ mock_data = {}
103
+ if include_body_if_none or body is not None:
104
+ mock_data["body"] = body
105
+ if status_code is not None:
106
+ mock_data["status_code"] = status_code
107
+ if headers is not None:
108
+ mock_data["headers"] = headers
109
+
110
+ request_data = {"path": path}
111
+ if methods is not None:
112
+ request_data["methods"] = methods
113
+ if mock_data:
114
+ request_data["mock_data"] = mock_data
115
+ if extra_info is not None:
116
+ request_data["extra_info"] = extra_info
117
+ if proxy_host is not None:
118
+ request_data["proxy_host"] = proxy_host
119
+ if timeout is not None:
120
+ request_data["timeout"] = timeout
121
+ if rules is not None:
122
+ request_data["rules"] = rules
123
+ if cache_time is not None:
124
+ request_data["cache_time"] = cache_time
125
+
126
+ request_data.update(kwargs)
127
+ return request_data
128
+
129
+ async def _send_configure(self, method: HTTPMethod, payload: dict):
130
+ return await self.execute_request_and_get_response_body(
131
+ method=method,
132
+ route=Endpoints.CONFIGURE_MOCK,
133
+ content=msgpack.packb(payload),
134
+ headers={"Content-Type": CONFIGURE_CONTENT_TYPE},
135
+ )
136
+
137
+ async def get_proxy_mock(self):
138
+ return await self.execute_request_and_get_response_body(HTTPMethod.GET, Endpoints.PROXY_MOCK)
139
+
140
+ async def configure_mock(
141
+ self,
142
+ path: str,
143
+ body: Any = None,
144
+ headers: dict | None = None,
145
+ status_code: int | None = None,
146
+ extra_info: dict | None = None,
147
+ proxy_host: str | None = None,
148
+ timeout: float | None = None,
149
+ rules: list[dict] | None = None,
150
+ methods: list[str] | None = None,
151
+ cache_time: int | None = None,
152
+ **kwargs,
153
+ ):
154
+ payload = self._build_mock_request_data(
155
+ path=path,
156
+ body=body,
157
+ headers=headers,
158
+ status_code=status_code,
159
+ extra_info=extra_info,
160
+ proxy_host=proxy_host,
161
+ timeout=timeout,
162
+ rules=rules,
163
+ methods=methods,
164
+ cache_time=cache_time,
165
+ include_body_if_none=True,
166
+ **kwargs,
167
+ )
168
+ return await self._send_configure(HTTPMethod.POST, payload)
169
+
170
+ async def patch_mock(
171
+ self,
172
+ path: str,
173
+ body: Any = None,
174
+ headers: dict | None = None,
175
+ status_code: int | None = None,
176
+ extra_info: dict | None = None,
177
+ proxy_host: str | None = None,
178
+ timeout: float | None = None,
179
+ rules: list[dict] | None = None,
180
+ methods: list[str] | None = None,
181
+ cache_time: int | None = None,
182
+ **kwargs,
183
+ ):
184
+ payload = self._build_mock_request_data(
185
+ path=path,
186
+ body=body,
187
+ headers=headers,
188
+ status_code=status_code,
189
+ extra_info=extra_info,
190
+ proxy_host=proxy_host,
191
+ timeout=timeout,
192
+ rules=rules,
193
+ methods=methods,
194
+ cache_time=cache_time,
195
+ include_body_if_none=False,
196
+ **kwargs,
197
+ )
198
+ return await self._send_configure(HTTPMethod.PATCH, payload)
199
+
200
+ async def get_traffic(self, path: str | None = None, method: str | None = None, limit: int | None = None):
201
+ query_params = {
202
+ **({"path": path} if path else {}),
203
+ **({"method": method} if method else {}),
204
+ **({"limit": limit} if limit is not None else {}),
205
+ }
206
+ full_path = URL().with_path(Endpoints.TRAFFIC).with_query(query_params).human_repr()
207
+ return await self.execute_request_and_get_response_body(HTTPMethod.GET, full_path)
208
+
209
+ async def get_storage(self, path: str | None = None):
210
+ query_params = {**({"path": path} if path else {})}
211
+ full_path = URL().with_path(Endpoints.STORAGE).with_query(query_params).human_repr()
212
+ return await self.execute_request_and_get_response_body(HTTPMethod.GET, full_path)
213
+
214
+ async def clean_storage(self, path: str | None = None):
215
+ query_params = {**({"path": path} if path else {})}
216
+ full_path = URL().with_path(Endpoints.STORAGE_CLEAN).with_query(query_params).human_repr()
217
+ return await self.execute_request_and_get_response_body(HTTPMethod.POST, full_path)
218
+
219
+ async def delete_mock(self, path: str):
220
+ full_path = URL().with_path(Endpoints.STORAGE).with_query({"path": path}).human_repr()
221
+ return await self.execute_request_and_get_response_body(HTTPMethod.DELETE, full_path)
222
+
223
+ async def clean_traffic(self):
224
+ return await self.execute_request_and_get_response_body(HTTPMethod.POST, Endpoints.TRAFFIC_CLEAN)
225
+
226
+ async def get_traffic_settings(self):
227
+ return await self.execute_request_and_get_response_body(HTTPMethod.GET, Endpoints.TRAFFIC_SETTINGS)
228
+
229
+ async def set_traffic_settings(self, record_unknown_traffic: bool | None = None, max_items: int | None = None):
230
+ return await self.execute_request_and_get_response_body(
231
+ HTTPMethod.POST,
232
+ Endpoints.TRAFFIC_SETTINGS,
233
+ json=_build_traffic_settings_payload(record_unknown_traffic, max_items),
234
+ )
235
+
236
+ async def clean_cache(self):
237
+ return await self.execute_request_and_get_response_body(HTTPMethod.POST, Endpoints.CACHE_CLEAN)