"""
askpablos_api.validators
Parameter validation utilities for the AskPablos API client.
This module contains validation logic for request parameters, ensuring
proper parameter combinations and providing clear error messages.
Separating validation logic improves code clarity and reusability.
"""
from typing import Dict, Any, Optional
from .exceptions import ConfigurationError
[docs]
class ParameterValidator:
"""
Validates request parameters and options for API calls.
This class provides centralized validation logic to ensure that
parameter combinations are valid and provide helpful error messages
when invalid combinations are detected.
"""
[docs]
@staticmethod
def validate_browser_dependencies(
browser: bool,
screenshot: bool = False
) -> None:
"""
Validate that browser-dependent features are only used with browser=True.
The following parameters require browser=True to function:
- screenshot: Requires browser automation to capture page screenshots
Args:
browser: Whether browser mode is enabled
screenshot: Whether screenshot capture is requested
Raises:
ConfigurationError: If browser features are requested without browser=True
"""
if browser:
return # All features are valid when browser is enabled
if screenshot:
raise ConfigurationError(
f"CONFIGURATION ERROR: browser=True is required when using: screenshot=True."
)
[docs]
@staticmethod
def validate_method(method: str) -> None:
"""
Validate that the HTTP method is supported.
Args:
method: HTTP method to validate
Raises:
ValueError: If the method is not one of the supported values
"""
if not isinstance(method, str):
raise ValueError("Method must be a string")
valid_methods = ['GET', 'POST']
if method.upper() not in valid_methods:
raise ValueError(
f"Method must be one of {valid_methods}, got '{method}'"
)
[docs]
@staticmethod
def validate_body(method: str, body: Optional[Any]) -> None:
"""
Validate that a request body is only used with POST requests.
Args:
method: HTTP method for the request
body: Request body to validate
Raises:
ConfigurationError: If body is provided for a non-POST request
"""
if body is not None and method.upper() != 'POST':
raise ConfigurationError(
"CONFIGURATION ERROR: 'body' can only be used when method='POST'."
)
[docs]
@staticmethod
def validate_url(url: str) -> None:
"""
Validate that the URL is properly formatted.
Args:
url: URL to validate
Raises:
ValueError: If URL is invalid
"""
if not url:
raise ValueError("URL is required and cannot be empty")
if not isinstance(url, str):
raise ValueError("URL must be a string")
# Basic URL validation
if not (url.startswith('http://') or url.startswith('https://')):
raise ValueError("URL must start with 'http://' or 'https://'")
[docs]
@staticmethod
def validate_timeout(timeout: int) -> None:
"""
Validate timeout parameter.
Args:
timeout: Timeout value in seconds
Raises:
ValueError: If timeout is invalid
"""
if not isinstance(timeout, int):
raise ValueError("Timeout must be an integer")
if timeout <= 0:
raise ValueError("Timeout must be greater than 0")
if timeout > 300: # 5 minutes max
raise ValueError("Timeout cannot exceed 300 seconds")
[docs]
@staticmethod
def validate_geo_location(config: Dict[str, Any], validated_config: Dict[str, Any]) -> None:
"""
Validate geoLocation configuration.
Args:
config: Raw configuration dictionary
validated_config: Dictionary to store validated config
Raises:
ValueError: If geoLocation value is invalid
"""
if 'geoLocation' in config:
geo = config['geoLocation']
if not isinstance(geo, str):
raise ValueError("'geoLocation' must be a string")
geo = geo.strip().lower()
if len(geo) != 2 or not geo.isalpha():
raise ValueError(
f"'geoLocation' must be a 2-letter ISO country code (e.g. 'US', 'PK', 'GB'), got '{config['geoLocation']}'"
)
validated_config['geoLocation'] = geo
[docs]
@staticmethod
def validate_proxy_type(config: Dict[str, Any], validated_config: Dict[str, Any]) -> None:
"""
Validate proxyType configuration.
Args:
config: Raw configuration dictionary
validated_config: Dictionary to store validated config
Raises:
ValueError: If proxyType value is invalid
"""
if 'proxyType' in config:
proxy_type = config['proxyType']
valid_proxy_types = ['datacenter', 'residential', 'mobile']
if not isinstance(proxy_type, str):
raise ValueError("'proxyType' must be a string")
if proxy_type not in valid_proxy_types:
raise ValueError(
f"'proxyType' must be one of {valid_proxy_types}, got '{proxy_type}'"
)
validated_config['proxyType'] = proxy_type
[docs]
@classmethod
def validate_request_params(
cls,
url: str,
headers: Optional[Dict[str, str]] = None,
browser: bool = False,
screenshot: bool = False,
timeout: int = 30,
method: str = "GET",
body: Optional[Any] = None,
options: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""
Validate all request parameters at once.
Args:
url: Target URL
headers: Custom headers
browser: Browser mode flag
screenshot: Screenshot capture flag
timeout: Request timeout
method: HTTP method (GET or POST)
body: Request body, only valid when method='POST'
options: Additional options (geoLocation, proxyType, etc.)
Returns:
Dict[str, Any]: Validated and normalized options
Raises:
ValueError: If any parameter is invalid
"""
cls.validate_url(url)
cls.validate_headers(headers)
cls.validate_timeout(timeout)
cls.validate_browser_dependencies(
browser=browser,
screenshot=screenshot
)
cls.validate_method(method)
cls.validate_body(method, body)
validated_config: Dict[str, Any] = {}
if options:
cls.validate_geo_location(options, validated_config)
cls.validate_proxy_type(options, validated_config)
return validated_config