2024-09-29 05:04:10 +00:00
|
|
|
import httpx
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
class RecordingAsyncClient:
|
|
|
|
|
def __init__(self, save_dir: str):
|
|
|
|
|
self.save_dir = save_dir
|
|
|
|
|
os.makedirs(self.save_dir, exist_ok=True)
|
|
|
|
|
self.client = None # Will be initialized in __aenter__
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def __aenter__(self):
|
|
|
|
|
# Initialize the actual AsyncClient when entering the context manager
|
|
|
|
|
self.client = httpx.AsyncClient()
|
|
|
|
|
return self
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
|
|
|
# Ensure the client is closed when exiting the context manager
|
|
|
|
|
await self.client.aclose()
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def request(self, method: str, url: str, **kwargs):
|
|
|
|
|
# Send the actual request
|
|
|
|
|
response = await self.client.request(method, url, **kwargs)
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Record the request and response
|
|
|
|
|
record = {
|
|
|
|
|
"request": {
|
|
|
|
|
"method": method,
|
|
|
|
|
"url": url,
|
|
|
|
|
"headers": dict(response.request.headers),
|
2025-10-18 03:26:42 +00:00
|
|
|
"content": response.request.content.decode("utf-8", errors="ignore"),
|
2024-09-29 05:04:10 +00:00
|
|
|
},
|
|
|
|
|
"response": {
|
|
|
|
|
"status_code": response.status_code,
|
|
|
|
|
"headers": dict(response.headers),
|
|
|
|
|
"content": response.text,
|
|
|
|
|
"cookies": dict(response.cookies),
|
2025-10-18 03:26:42 +00:00
|
|
|
},
|
2024-09-29 05:04:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Generate a filename based on the URL and method
|
|
|
|
|
record_file = os.path.join(self.save_dir, f"{method}_{url.replace('/', '_')}.json")
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Save the record to a file
|
2025-10-18 03:26:42 +00:00
|
|
|
with open(record_file, "w") as f:
|
2024-09-29 05:04:10 +00:00
|
|
|
json.dump(record, f, indent=4)
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
async def get(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("GET", url, **kwargs)
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def post(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("POST", url, **kwargs)
|
|
|
|
|
|
|
|
|
|
async def put(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("PUT", url, **kwargs)
|
|
|
|
|
|
|
|
|
|
async def delete(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("DELETE", url, **kwargs)
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
from unittest.mock import Mock
|
|
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
class MockAsyncClient:
|
|
|
|
|
def __init__(self, load_dir: str):
|
|
|
|
|
self.load_dir = load_dir
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def __aenter__(self):
|
|
|
|
|
# No actual client to initialize, just return the instance
|
|
|
|
|
return self
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
|
|
|
# No actual client to close
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
async def request(self, method: str, url: str, **kwargs):
|
|
|
|
|
# Generate the filename based on the URL and method
|
|
|
|
|
record_file = os.path.join(self.load_dir, f"{method}_{url.replace('/', '_')}.json")
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
if not os.path.exists(record_file):
|
|
|
|
|
raise FileNotFoundError(f"Recorded response not found for {method} {url}")
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Load the recorded response from the file
|
2025-10-18 03:26:42 +00:00
|
|
|
with open(record_file, "r") as f:
|
2024-09-29 05:04:10 +00:00
|
|
|
record = json.load(f)
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Create a mock response object
|
|
|
|
|
mock_response = Mock()
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Mock the status code
|
2025-10-18 03:26:42 +00:00
|
|
|
mock_response.status_code = record["response"]["status_code"]
|
|
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Mock the json method to return the content as a parsed JSON
|
|
|
|
|
def mock_json():
|
|
|
|
|
try:
|
2025-10-18 03:26:42 +00:00
|
|
|
return json.loads(record["response"]["content"])
|
2024-09-29 05:04:10 +00:00
|
|
|
except json.JSONDecodeError:
|
2025-10-18 03:26:42 +00:00
|
|
|
return record["response"]["content"]
|
|
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
mock_response.json = mock_json
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Mock the cookies as a dictionary
|
2025-10-18 03:26:42 +00:00
|
|
|
mock_response.cookies = record["response"]["cookies"]
|
2024-09-29 05:04:10 +00:00
|
|
|
|
|
|
|
|
# Mock the headers as a dictionary
|
2025-10-18 03:26:42 +00:00
|
|
|
mock_response.headers = record["response"]["headers"]
|
|
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
# Mock the text attribute
|
2025-10-18 03:26:42 +00:00
|
|
|
mock_response.text = record["response"]["content"]
|
|
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
return mock_response
|
|
|
|
|
|
|
|
|
|
async def get(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("GET", url, **kwargs)
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2024-09-29 05:04:10 +00:00
|
|
|
async def post(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("POST", url, **kwargs)
|
|
|
|
|
|
|
|
|
|
async def put(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("PUT", url, **kwargs)
|
|
|
|
|
|
|
|
|
|
async def delete(self, url: str, **kwargs):
|
|
|
|
|
return await self.request("DELETE", url, **kwargs)
|