1616
1717import boto3
1818import httpx
19+ import logging
1920import os
20- from httpx_auth_awssigv4 import SigV4Auth
21- from loguru import logger
22- from typing import Optional
21+ from botocore .auth import SigV4Auth
22+ from botocore .awsrequest import AWSRequest
23+ from botocore .credentials import Credentials
24+ from typing import Any , Dict , Generator , Optional
25+
26+
27+ logger = logging .getLogger (__name__ )
28+
29+
30+ class SigV4HTTPXAuth (httpx .Auth ):
31+ """HTTPX Auth class that signs requests with AWS SigV4."""
32+
33+ def __init__ (
34+ self ,
35+ credentials : Credentials ,
36+ service : str ,
37+ region : str ,
38+ ):
39+ """Initialize SigV4HTTPXAuth.
40+
41+ Args:
42+ credentials: AWS credentials to use for signing
43+ service: AWS service name to sign requests for
44+ region: AWS region to sign requests for
45+ """
46+ self .credentials = credentials
47+ self .service = service
48+ self .region = region
49+ self .signer = SigV4Auth (credentials , service , region )
50+
51+ def auth_flow (self , request : httpx .Request ) -> Generator [httpx .Request , httpx .Response , None ]:
52+ """Signs the request with SigV4 and adds the signature to the request headers."""
53+ # Create an AWS request
54+ headers = dict (request .headers )
55+ # Header 'connection' = 'keep-alive' is not used in calculating the request
56+ # signature on the server-side, and results in a signature mismatch if included
57+ headers .pop ('connection' , None ) # Remove if present, ignore if not
58+
59+ aws_request = AWSRequest (
60+ method = request .method ,
61+ url = str (request .url ),
62+ data = request .content ,
63+ headers = headers ,
64+ )
65+
66+ # Sign the request with SigV4
67+ self .signer .add_auth (aws_request )
68+
69+ # Add the signature header to the original request
70+ request .headers .update (dict (aws_request .headers ))
71+
72+ yield request
2373
2474
2575async def _handle_error_response (response : httpx .Response ) -> None :
@@ -39,24 +89,24 @@ async def _handle_error_response(response: httpx.Response) -> None:
3989 # Read response content to extract error details
4090 await response .aread ()
4191 except Exception as e :
42- logger .error (f 'Failed to read response: { e } ' )
92+ logger .error ('Failed to read response: %s' , e )
4393
4494 # Try to extract error details with fallbacks
4595 error_msg = ''
4696 try :
4797 # Try to parse JSON error details
4898 error_details = response .json ()
49- logger .error (f 'HTTP { response . status_code } Error Details: { error_details } ' )
99+ logger .error ('HTTP %d Error Details: %s' , response . status_code , error_details )
50100 error_msg = f'HTTP { response .status_code } : { error_details } for url { response .url } '
51101 except Exception :
52102 # If JSON parsing fails, use response text or status code
53103 try :
54104 response_text = response .text
55- logger .error (f 'HTTP { response . status_code } Error: { response_text } ' )
105+ logger .error ('HTTP %d Error: %s' , response . status_code , response_text )
56106 error_msg = f'HTTP { response .status_code } : { response_text } for url { response .url } '
57107 except Exception :
58108 # Fallback to just status code and URL
59- logger .error (f 'HTTP { response . status_code } Error for url { response .url } ' )
109+ logger .error ('HTTP %d Error for url %s' , response .status_code , response . url )
60110 error_msg = f'HTTP { response .status_code } Error for url { response .url } '
61111
62112 # Raise the status error with enhanced message
@@ -102,7 +152,7 @@ def create_aws_session(profile: Optional[str] = None) -> boto3.Session:
102152
103153def create_sigv4_auth (
104154 service : str , profile : Optional [str ] = None , region : Optional [str ] = None
105- ) -> SigV4Auth :
155+ ) -> SigV4HTTPXAuth :
106156 """Create SigV4 authentication for AWS requests.
107157
108158 Args:
@@ -111,7 +161,7 @@ def create_sigv4_auth(
111161 region: AWS region (defaults to AWS_REGION env var or us-west-2)
112162
113163 Returns:
114- SigV4Auth instance
164+ SigV4HTTPXAuth instance
115165
116166 Raises:
117167 ValueError: If credentials cannot be obtained
@@ -125,25 +175,23 @@ def create_sigv4_auth(
125175 region = os .environ .get ('AWS_REGION' , 'us-west-2' )
126176
127177 # Create SigV4Auth with explicit credentials
128- sigv4_auth = SigV4Auth (
129- access_key = credentials .access_key ,
130- secret_key = credentials .secret_key ,
178+ sigv4_auth = SigV4HTTPXAuth (
179+ credentials = credentials ,
131180 service = service ,
132181 region = region ,
133- token = credentials .token ,
134182 )
135183
136- logger .info (f "Created SigV4 authentication for service '{ service } ' in region '{ region } '" )
184+ logger .info ("Created SigV4 authentication for service '%s ' in region '%s'" , service , region )
137185 return sigv4_auth
138186
139187
140188def create_sigv4_client (
141189 service : str = 'eks-mcp' ,
142190 profile : Optional [str ] = None ,
143191 region : Optional [str ] = None ,
144- headers = None ,
145- auth = None ,
146- ** kwargs ,
192+ headers : Optional [ Dict [ str , str ]] = None ,
193+ auth : Optional [ httpx . Auth ] = None ,
194+ ** kwargs : Any ,
147195) -> httpx .AsyncClient :
148196 """Create an httpx.AsyncClient with SigV4 authentication.
149197
@@ -180,7 +228,7 @@ def create_sigv4_client(
180228 sigv4_auth = create_sigv4_auth (service , profile , region )
181229
182230 # Create the client with SigV4 auth and error handling event hook
183- logger .info (f "Creating httpx.AsyncClient with SigV4 authentication for service '{ service } '" )
231+ logger .info ("Creating httpx.AsyncClient with SigV4 authentication for service '%s'" , service )
184232
185233 return httpx .AsyncClient (
186234 auth = sigv4_auth ,
0 commit comments