Files
attune/packs/core/actions/http_request.py
2026-02-04 17:46:30 -06:00

207 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""
HTTP Request Action - Core Pack
Make HTTP requests to external APIs with support for various methods, headers, and authentication
"""
import json
import os
import sys
import time
from typing import Any, Dict, Optional
try:
import requests
from requests.auth import HTTPBasicAuth
except ImportError:
print(
"ERROR: requests library not installed. Run: pip install requests>=2.28.0",
file=sys.stderr,
)
sys.exit(1)
def get_env_param(name: str, default: Any = None, required: bool = False) -> Any:
"""Get action parameter from environment variable."""
env_key = f"ATTUNE_ACTION_{name.upper()}"
value = os.environ.get(env_key, default)
if required and value is None:
raise ValueError(f"Required parameter '{name}' not provided")
return value
def parse_json_param(name: str, default: Any = None) -> Any:
"""Parse JSON parameter from environment variable."""
value = get_env_param(name)
if value is None:
return default
try:
return json.loads(value)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON for parameter '{name}': {e}")
def parse_bool_param(name: str, default: bool = False) -> bool:
"""Parse boolean parameter from environment variable."""
value = get_env_param(name)
if value is None:
return default
if isinstance(value, bool):
return value
return str(value).lower() in ("true", "1", "yes", "on")
def parse_int_param(name: str, default: int = 0) -> int:
"""Parse integer parameter from environment variable."""
value = get_env_param(name)
if value is None:
return default
try:
return int(value)
except (ValueError, TypeError):
raise ValueError(f"Invalid integer for parameter '{name}': {value}")
def make_http_request() -> Dict[str, Any]:
"""Execute HTTP request with provided parameters."""
# Parse required parameters
url = get_env_param("url", required=True)
# Parse optional parameters
method = get_env_param("method", "GET").upper()
headers = parse_json_param("headers", {})
body = get_env_param("body")
json_body = parse_json_param("json_body")
query_params = parse_json_param("query_params", {})
timeout = parse_int_param("timeout", 30)
verify_ssl = parse_bool_param("verify_ssl", True)
auth_type = get_env_param("auth_type", "none")
follow_redirects = parse_bool_param("follow_redirects", True)
max_redirects = parse_int_param("max_redirects", 10)
# Prepare request kwargs
request_kwargs = {
"method": method,
"url": url,
"headers": headers,
"params": query_params,
"timeout": timeout,
"verify": verify_ssl,
"allow_redirects": follow_redirects,
}
# Handle authentication
if auth_type == "basic":
username = get_env_param("auth_username")
password = get_env_param("auth_password")
if username and password:
request_kwargs["auth"] = HTTPBasicAuth(username, password)
elif auth_type == "bearer":
token = get_env_param("auth_token")
if token:
request_kwargs["headers"]["Authorization"] = f"Bearer {token}"
# Handle request body
if json_body is not None:
request_kwargs["json"] = json_body
elif body is not None:
request_kwargs["data"] = body
# Make the request
start_time = time.time()
try:
response = requests.request(**request_kwargs)
elapsed_ms = int((time.time() - start_time) * 1000)
# Parse response
result = {
"status_code": response.status_code,
"headers": dict(response.headers),
"body": response.text,
"elapsed_ms": elapsed_ms,
"url": response.url,
"success": 200 <= response.status_code < 300,
}
# Try to parse JSON response
try:
result["json"] = response.json()
except (json.JSONDecodeError, ValueError):
result["json"] = None
return result
except requests.exceptions.Timeout:
return {
"status_code": 0,
"headers": {},
"body": "",
"json": None,
"elapsed_ms": int((time.time() - start_time) * 1000),
"url": url,
"success": False,
"error": "Request timeout",
}
except requests.exceptions.ConnectionError as e:
return {
"status_code": 0,
"headers": {},
"body": "",
"json": None,
"elapsed_ms": int((time.time() - start_time) * 1000),
"url": url,
"success": False,
"error": f"Connection error: {str(e)}",
}
except requests.exceptions.RequestException as e:
return {
"status_code": 0,
"headers": {},
"body": "",
"json": None,
"elapsed_ms": int((time.time() - start_time) * 1000),
"url": url,
"success": False,
"error": f"Request error: {str(e)}",
}
def main():
"""Main entry point for the action."""
try:
result = make_http_request()
# Output result as JSON
print(json.dumps(result, indent=2))
# Exit with success/failure based on HTTP status
if result.get("success", False):
sys.exit(0)
else:
# Non-2xx status code or error
error = result.get("error")
if error:
print(f"ERROR: {error}", file=sys.stderr)
else:
print(
f"ERROR: HTTP request failed with status {result.get('status_code')}",
file=sys.stderr,
)
sys.exit(1)
except Exception as e:
print(f"ERROR: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()