fix httpx efficiency
This commit is contained in:
+94
-75
@@ -348,6 +348,19 @@ from fastmcp import FastMCP
|
|||||||
# Placeholder app for decorators - will be replaced in create_app() after all tools are defined
|
# Placeholder app for decorators - will be replaced in create_app() after all tools are defined
|
||||||
app = FastMCP("Yargı MCP Server Placeholder")
|
app = FastMCP("Yargı MCP Server Placeholder")
|
||||||
|
|
||||||
|
# --- Shared HTTP Client for Health Checks ---
|
||||||
|
shared_health_check_client = None
|
||||||
|
|
||||||
|
def get_or_create_health_check_client():
|
||||||
|
"""Get or create shared httpx client for health checks."""
|
||||||
|
global shared_health_check_client
|
||||||
|
if shared_health_check_client is None:
|
||||||
|
shared_health_check_client = httpx.AsyncClient(
|
||||||
|
timeout=30.0,
|
||||||
|
verify=False
|
||||||
|
)
|
||||||
|
return shared_health_check_client
|
||||||
|
|
||||||
# --- API Client Instances ---
|
# --- API Client Instances ---
|
||||||
yargitay_client_instance = YargitayOfficialApiClient()
|
yargitay_client_instance = YargitayOfficialApiClient()
|
||||||
danistay_client_instance = DanistayApiClient()
|
danistay_client_instance = DanistayApiClient()
|
||||||
@@ -1417,6 +1430,14 @@ def perform_cleanup():
|
|||||||
]
|
]
|
||||||
async def close_all_clients_async():
|
async def close_all_clients_async():
|
||||||
tasks = []
|
tasks = []
|
||||||
|
|
||||||
|
# Close shared health check client first
|
||||||
|
global shared_health_check_client
|
||||||
|
if shared_health_check_client:
|
||||||
|
logger.info("Scheduling close for shared health check client")
|
||||||
|
tasks.append(shared_health_check_client.aclose())
|
||||||
|
|
||||||
|
# Close all module clients
|
||||||
for client_instance in clients_to_close:
|
for client_instance in clients_to_close:
|
||||||
if client_instance and hasattr(client_instance, 'close_client_session') and callable(client_instance.close_client_session):
|
if client_instance and hasattr(client_instance, 'close_client_session') and callable(client_instance.close_client_session):
|
||||||
logger.info(f"Scheduling close for client session: {client_instance.__class__.__name__}")
|
logger.info(f"Scheduling close for client session: {client_instance.__class__.__name__}")
|
||||||
@@ -1467,50 +1488,49 @@ async def check_government_servers_health() -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async with httpx.AsyncClient(
|
client = get_or_create_health_check_client()
|
||||||
headers={
|
headers = {
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
"Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
"Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
"Connection": "keep-alive",
|
"Connection": "keep-alive",
|
||||||
"Content-Type": "application/json; charset=UTF-8",
|
"Content-Type": "application/json; charset=UTF-8",
|
||||||
"Origin": "https://karararama.yargitay.gov.tr",
|
"Origin": "https://karararama.yargitay.gov.tr",
|
||||||
"Referer": "https://karararama.yargitay.gov.tr/",
|
"Referer": "https://karararama.yargitay.gov.tr/",
|
||||||
"Sec-Fetch-Dest": "empty",
|
"Sec-Fetch-Dest": "empty",
|
||||||
"Sec-Fetch-Mode": "cors",
|
"Sec-Fetch-Mode": "cors",
|
||||||
"Sec-Fetch-Site": "same-origin",
|
"Sec-Fetch-Site": "same-origin",
|
||||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||||
"X-Requested-With": "XMLHttpRequest"
|
"X-Requested-With": "XMLHttpRequest"
|
||||||
},
|
}
|
||||||
timeout=30.0,
|
|
||||||
verify=False
|
response = await client.post(
|
||||||
) as client:
|
"https://karararama.yargitay.gov.tr/aramalist",
|
||||||
response = await client.post(
|
json=yargitay_payload,
|
||||||
"https://karararama.yargitay.gov.tr/aramalist",
|
headers=headers
|
||||||
json=yargitay_payload
|
)
|
||||||
)
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_data = response.json()
|
||||||
|
records_total = response_data.get("data", {}).get("recordsTotal", 0)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if records_total > 0:
|
||||||
response_data = response.json()
|
health_results["yargitay"] = {
|
||||||
records_total = response_data.get("data", {}).get("recordsTotal", 0)
|
"status": "healthy",
|
||||||
|
"response_time_ms": response.elapsed.total_seconds() * 1000
|
||||||
if records_total > 0:
|
}
|
||||||
health_results["yargitay"] = {
|
|
||||||
"status": "healthy",
|
|
||||||
"response_time_ms": response.elapsed.total_seconds() * 1000
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
health_results["yargitay"] = {
|
|
||||||
"status": "unhealthy",
|
|
||||||
"reason": "recordsTotal is 0 or missing",
|
|
||||||
"response_time_ms": response.elapsed.total_seconds() * 1000
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
health_results["yargitay"] = {
|
health_results["yargitay"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
"reason": f"HTTP {response.status_code}",
|
"reason": "recordsTotal is 0 or missing",
|
||||||
"response_time_ms": response.elapsed.total_seconds() * 1000
|
"response_time_ms": response.elapsed.total_seconds() * 1000
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
|
health_results["yargitay"] = {
|
||||||
|
"status": "unhealthy",
|
||||||
|
"reason": f"HTTP {response.status_code}",
|
||||||
|
"response_time_ms": response.elapsed.total_seconds() * 1000
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
health_results["yargitay"] = {
|
health_results["yargitay"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
@@ -1532,50 +1552,49 @@ async def check_government_servers_health() -> Dict[str, Any]:
|
|||||||
"paging": True
|
"paging": True
|
||||||
}
|
}
|
||||||
|
|
||||||
async with httpx.AsyncClient(
|
client = get_or_create_health_check_client()
|
||||||
headers={
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"User-Agent": "Mozilla/5.0 Health Check"
|
"User-Agent": "Mozilla/5.0 Health Check"
|
||||||
},
|
}
|
||||||
timeout=30.0,
|
|
||||||
verify=False
|
response = await client.post(
|
||||||
) as client:
|
"https://bedesten.adalet.gov.tr/emsal-karar/searchDocuments",
|
||||||
response = await client.post(
|
json=bedesten_payload,
|
||||||
"https://bedesten.adalet.gov.tr/emsal-karar/searchDocuments",
|
headers=headers
|
||||||
json=bedesten_payload
|
)
|
||||||
)
|
|
||||||
|
if response.status_code == 200:
|
||||||
if response.status_code == 200:
|
response_data = response.json()
|
||||||
response_data = response.json()
|
logger.debug(f"Bedesten API response: {response_data}")
|
||||||
logger.debug(f"Bedesten API response: {response_data}")
|
if response_data and isinstance(response_data, dict):
|
||||||
if response_data and isinstance(response_data, dict):
|
data_section = response_data.get("data")
|
||||||
data_section = response_data.get("data")
|
if data_section and isinstance(data_section, dict):
|
||||||
if data_section and isinstance(data_section, dict):
|
total_found = data_section.get("total", 0)
|
||||||
total_found = data_section.get("total", 0)
|
|
||||||
else:
|
|
||||||
total_found = 0
|
|
||||||
else:
|
else:
|
||||||
total_found = 0
|
total_found = 0
|
||||||
|
else:
|
||||||
if total_found > 0:
|
total_found = 0
|
||||||
health_results["bedesten"] = {
|
|
||||||
"status": "healthy",
|
if total_found > 0:
|
||||||
"response_time_ms": response.elapsed.total_seconds() * 1000
|
health_results["bedesten"] = {
|
||||||
}
|
"status": "healthy",
|
||||||
else:
|
"response_time_ms": response.elapsed.total_seconds() * 1000
|
||||||
health_results["bedesten"] = {
|
}
|
||||||
"status": "unhealthy",
|
|
||||||
"reason": "total is 0 or missing in data field",
|
|
||||||
"response_time_ms": response.elapsed.total_seconds() * 1000
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
health_results["bedesten"] = {
|
health_results["bedesten"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
"reason": f"HTTP {response.status_code}",
|
"reason": "total is 0 or missing in data field",
|
||||||
"response_time_ms": response.elapsed.total_seconds() * 1000
|
"response_time_ms": response.elapsed.total_seconds() * 1000
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
|
health_results["bedesten"] = {
|
||||||
|
"status": "unhealthy",
|
||||||
|
"reason": f"HTTP {response.status_code}",
|
||||||
|
"response_time_ms": response.elapsed.total_seconds() * 1000
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
health_results["bedesten"] = {
|
health_results["bedesten"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# uyusmazlik_mcp_module/client.py
|
# uyusmazlik_mcp_module/client.py
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import aiohttp
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from typing import Dict, Any, List, Optional, Union, Tuple
|
from typing import Dict, Any, List, Optional, Union, Tuple
|
||||||
import logging
|
import logging
|
||||||
@@ -9,7 +8,7 @@ import html
|
|||||||
import re
|
import re
|
||||||
import io
|
import io
|
||||||
from markitdown import MarkItDown
|
from markitdown import MarkItDown
|
||||||
from urllib.parse import urljoin, urlencode # urlencode for aiohttp form data
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
UyusmazlikSearchRequest,
|
UyusmazlikSearchRequest,
|
||||||
@@ -56,17 +55,21 @@ class UyusmazlikApiClient:
|
|||||||
# Individual documents are fetched by their full URLs obtained from search results.
|
# Individual documents are fetched by their full URLs obtained from search results.
|
||||||
|
|
||||||
def __init__(self, request_timeout: float = 30.0):
|
def __init__(self, request_timeout: float = 30.0):
|
||||||
self.request_timeout = request_timeout # Store timeout for aiohttp and httpx
|
self.request_timeout = request_timeout
|
||||||
# Headers for aiohttp search. httpx for docs will create its own.
|
# Create shared httpx client for all requests
|
||||||
self.default_aiohttp_search_headers = {
|
self.http_client = httpx.AsyncClient(
|
||||||
"Accept": "*/*", # Mimicking browser headers provided by user
|
base_url=self.BASE_URL,
|
||||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
headers={
|
||||||
"Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
"Accept": "*/*",
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||||
"Origin": self.BASE_URL,
|
"Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
"Referer": self.BASE_URL + "/",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"Origin": self.BASE_URL,
|
||||||
}
|
"Referer": self.BASE_URL + "/",
|
||||||
|
},
|
||||||
|
timeout=request_timeout,
|
||||||
|
verify=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def search_decisions(
|
async def search_decisions(
|
||||||
@@ -107,32 +110,36 @@ class UyusmazlikApiClient:
|
|||||||
add_to_form_data("Hepsi", params.hepsi)
|
add_to_form_data("Hepsi", params.hepsi)
|
||||||
add_to_form_data("Herhangibirisi", params.herhangi_birisi)
|
add_to_form_data("Herhangibirisi", params.herhangi_birisi)
|
||||||
add_to_form_data("NotHepsi", params.not_hepsi)
|
add_to_form_data("NotHepsi", params.not_hepsi)
|
||||||
# X-Requested-With is handled by default_aiohttp_search_headers
|
|
||||||
|
|
||||||
search_url = urljoin(self.BASE_URL, self.SEARCH_ENDPOINT)
|
# Convert form data to dict for httpx
|
||||||
# For aiohttp, data for application/x-www-form-urlencoded should be a dict or str.
|
form_data_dict = {}
|
||||||
# Using urlencode for list of tuples.
|
for key, value in form_data_list:
|
||||||
encoded_form_payload = urlencode(form_data_list, encoding='UTF-8')
|
if key in form_data_dict:
|
||||||
|
# Handle multiple values (like KararSonucuList)
|
||||||
|
if not isinstance(form_data_dict[key], list):
|
||||||
|
form_data_dict[key] = [form_data_dict[key]]
|
||||||
|
form_data_dict[key].append(value)
|
||||||
|
else:
|
||||||
|
form_data_dict[key] = value
|
||||||
|
|
||||||
logger.info(f"UyusmazlikApiClient (aiohttp): Performing search to {search_url} with form_data: {encoded_form_payload}")
|
logger.info(f"UyusmazlikApiClient (httpx): Performing search to {self.SEARCH_ENDPOINT} with form_data: {form_data_dict}")
|
||||||
|
|
||||||
html_content = ""
|
|
||||||
aiohttp_headers = self.default_aiohttp_search_headers.copy()
|
|
||||||
aiohttp_headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create a new session for each call for simplicity with aiohttp here
|
# Use shared httpx client
|
||||||
async with aiohttp.ClientSession(headers=aiohttp_headers) as session:
|
response = await self.http_client.post(
|
||||||
async with session.post(search_url, data=encoded_form_payload, timeout=self.request_timeout) as response:
|
self.SEARCH_ENDPOINT,
|
||||||
response.raise_for_status() # Raises ClientResponseError for 400-599
|
data=form_data_dict,
|
||||||
html_content = await response.text(encoding='utf-8') # Ensure correct encoding
|
headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||||
logger.debug("UyusmazlikApiClient (aiohttp): Received HTML response for search.")
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
html_content = response.text
|
||||||
|
logger.debug("UyusmazlikApiClient (httpx): Received HTML response for search.")
|
||||||
|
|
||||||
except aiohttp.ClientError as e:
|
except httpx.HTTPError as e:
|
||||||
logger.error(f"UyusmazlikApiClient (aiohttp): HTTP client error during search: {e}")
|
logger.error(f"UyusmazlikApiClient (httpx): HTTP client error during search: {e}")
|
||||||
raise # Re-raise to be handled by the MCP tool
|
raise # Re-raise to be handled by the MCP tool
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"UyusmazlikApiClient (aiohttp): Error processing search request: {e}")
|
logger.error(f"UyusmazlikApiClient (httpx): Error processing search request: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# --- HTML Parsing (remains the same as previous version) ---
|
# --- HTML Parsing (remains the same as previous version) ---
|
||||||
@@ -215,10 +222,8 @@ class UyusmazlikApiClient:
|
|||||||
"""
|
"""
|
||||||
logger.info(f"UyusmazlikApiClient (httpx for docs): Fetching Uyuşmazlık document for Markdown from URL: {document_url}")
|
logger.info(f"UyusmazlikApiClient (httpx for docs): Fetching Uyuşmazlık document for Markdown from URL: {document_url}")
|
||||||
try:
|
try:
|
||||||
# Using a new httpx.AsyncClient instance for this GET request for simplicity
|
# Use the existing shared http_client instead of creating a new one
|
||||||
async with httpx.AsyncClient(verify=False, timeout=self.request_timeout) as doc_fetch_client:
|
get_response = await self.http_client.get(document_url, headers={"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"})
|
||||||
|
|
||||||
get_response = await doc_fetch_client.get(document_url, headers={"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"})
|
|
||||||
get_response.raise_for_status()
|
get_response.raise_for_status()
|
||||||
html_content_from_api = get_response.text
|
html_content_from_api = get_response.text
|
||||||
|
|
||||||
@@ -236,5 +241,9 @@ class UyusmazlikApiClient:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
async def close_client_session(self):
|
async def close_client_session(self):
|
||||||
|
"""Close the shared httpx client session."""
|
||||||
logger.info("UyusmazlikApiClient: No persistent client session from __init__ to close.")
|
if hasattr(self, 'http_client') and self.http_client:
|
||||||
|
await self.http_client.aclose()
|
||||||
|
logger.info("UyusmazlikApiClient: HTTP client session closed.")
|
||||||
|
else:
|
||||||
|
logger.info("UyusmazlikApiClient: No HTTP client session to close.")
|
||||||
Reference in New Issue
Block a user