66from  __future__ import  annotations 
77
88import  hashlib 
9- from  collections .abc  import  Generator 
9+ import  inspect 
10+ from  collections .abc  import  Awaitable , Callable , Generator 
1011from  copy  import  deepcopy 
11- from  typing  import  Any , TypeAlias , TypedDict , get_args 
12- from  collections .abc  import  Callable 
13- from  copy  import  deepcopy 
14- from  typing  import  Any , TypedDict , get_args 
12+ from  typing  import  TypedDict , get_args 
13+ from  typing_extensions  import  TypeIs 
1514
1615import  httpx 
1716import  pytest 
1817from  anthropic .lib .bedrock  import  _auth  as  bedrock_auth 
1918from  anthropic .lib .bedrock ._client  import  AnthropicBedrock , AsyncAnthropicBedrock 
19+ from  vcr .cassette  import  Cassette  as  VCRCassette 
20+ from  vcr .request  import  Request  as  VCRRequest 
2021from  vcr .stubs  import  httpx_stubs 
2122
2223from  mirascope  import  llm 
@@ -74,17 +75,28 @@ class VCRConfig(TypedDict, total=False):
7475    - 'uri': Request URI/URL 
7576    - 'body': Request body content (use 'raw_body' for exact binary matching) 
7677    - 'headers': Request headers 
78+     - 'scheme', 'host', 'port', 'path', 'query': URL components 
7779    """ 
7880
79-     before_record_request :  Callable [[ Any ],  Any ]
80-     """Callback  to sanitize requests before saving to cassette . 
81+     filter_headers :  list [ str ]
82+     """Headers  to filter out from recordings for security/privacy . 
8183
82-     This function is called AFTER the real HTTP request is sent (with valid auth), 
83-     but BEFORE it's written to the cassette file. Use this to sanitize sensitive 
84-     headers without affecting the actual HTTP requests. 
84+     DEPRECATED: Use before_record_request instead for better control. 
85+     These headers will be removed from both recorded cassettes and 
86+     when matching requests during playback. Commonly used for: 
87+     - Authentication tokens 
88+     - API keys 
89+     - Organization identifiers 
90+     """ 
91+ 
92+     filter_post_data_parameters : list [str ]
93+     """POST data parameters to filter out from recordings. 
94+ 
95+     Similar to filter_headers but for form data and request body parameters. 
96+     Useful for removing sensitive data from request bodies. 
8597    """ 
8698
87-     before_record_request : Any 
99+     before_record_request : Callable [[ VCRRequest ],  VCRRequest ] 
88100    """Callback to sanitize requests before saving to cassette. 
89101
90102    This function is called AFTER the real HTTP request is sent (with valid auth), 
@@ -101,17 +113,15 @@ class VCRConfig(TypedDict, total=False):
101113    """ 
102114
103115
104- 
105- def  sanitize_request (request : Any ) ->  Any :  # noqa: ANN401 
116+ def  sanitize_request (request : VCRRequest ) ->  VCRRequest :
106117    """Sanitize sensitive headers in VCR request before recording to cassette. 
107118
108119    This hook is called AFTER the real HTTP request is sent (with valid auth), 
109120    but BEFORE it's written to the cassette file. We deep copy the request 
110121    and replace sensitive headers with placeholders. 
111122
112123    Args: 
113-         request: VCR request object to sanitize (Any type since VCR doesn't 
114-             provide typed request objects) 
124+         request: VCR request object to sanitize 
115125
116126    Returns: 
117127        Sanitized copy of the request safe for cassette storage 
@@ -151,17 +161,14 @@ def vcr_config() -> VCRConfig:
151161    """ 
152162    return  {
153163        "record_mode" : "once" ,
154-         "match_on" : ["method" , "scheme " , "host"  ,  "port" ,  "path" ,  "query" ,  "raw_body "
164+         "match_on" : ["method" , "uri " , "body " ],
155165        "filter_headers" : [],  # Don't filter here; use before_record_request 
156166        "filter_post_data_parameters" : [],
157167        "before_record_request" : sanitize_request ,
158168        "decode_compressed_response" : False ,  # Preserve exact response bytes 
159169    }
160170
161171
162- Snapshot : TypeAlias  =  Any   # Alias to avoid Ruff lint errors 
163- 
164- 
165172def  _remove_auth_headers (headers : httpx .Headers ) ->  None :
166173    """Remove stale AWS authentication headers before re-signing. 
167174
@@ -286,7 +293,7 @@ async def _resign_async() -> None:
286293    original_sync_send  =  httpx_stubs ._sync_vcr_send 
287294    original_async_send  =  httpx_stubs ._async_vcr_send 
288295
289-     def  _is_bedrock_request (request : httpx .Request  |  None ) ->  bool :
296+     def  _is_bedrock_request (request : httpx .Request  |  None ) ->  TypeIs [ httpx . Request ] :
290297        """Check if the request is targeting AWS Bedrock API. 
291298
292299        We identify Bedrock requests by checking for 'bedrock-runtime' in the 
@@ -338,7 +345,7 @@ def _handle_bedrock_resign_sync(real_request: httpx.Request) -> None:
338345        _reset_request_body (real_request , body_bytes )
339346
340347        resign_sync  =  real_request .extensions .get ("mirascope_bedrock_resign" )
341-         if  callable (resign_sync ):
348+         if  inspect . isfunction (resign_sync ):
342349            _remove_auth_headers (real_request .headers )
343350            resign_sync ()
344351            _reset_request_body (real_request , body_bytes )
@@ -358,21 +365,27 @@ async def _handle_bedrock_resign_async(real_request: httpx.Request) -> None:
358365        resign_async  =  real_request .extensions .get ("mirascope_bedrock_resign_async" )
359366        resign_sync  =  real_request .extensions .get ("mirascope_bedrock_resign" )
360367
361-         if  callable (resign_async ):
368+         if  inspect . iscoroutinefunction (resign_async ):
362369            _remove_auth_headers (real_request .headers )
363370            await  resign_async ()
364371            _reset_request_body (real_request , body_bytes )
365-         elif  callable (resign_sync ):
372+         elif  inspect . isfunction (resign_sync ):
366373            _remove_auth_headers (real_request .headers )
367374            resign_sync ()
368375            _reset_request_body (real_request , body_bytes )
369376
370-     def  _patched_sync_send (cassette , real_send , * args , ** kwargs ) ->  httpx .Response :  # noqa: ANN001, ANN002, ANN003 
377+     def  _patched_sync_send (
378+         cassette : VCRCassette ,
379+         real_send : Callable [..., httpx .Response ],
380+         client : httpx .Client ,
381+         request : httpx .Request ,
382+         ** kwargs : object ,
383+     ) ->  httpx .Response :
384+         args  =  (client , request )
371385        vcr_request , response  =  original_shared_send (
372386            cassette , real_send , * args , ** kwargs 
373387        )
374-         client  =  args [0 ]
375-         real_request  =  args [1 ] if  len (args ) >  1  else  None 
388+         real_request : httpx .Request  |  None  =  request 
376389
377390        if  response  is  not None :
378391            client .cookies .extract_cookies (response )
@@ -392,16 +405,18 @@ def _patched_sync_send(cassette, real_send, *args, **kwargs) -> httpx.Response:
392405        return  real_response 
393406
394407    async  def  _patched_async_send (
395-         cassette ,
396-         real_send ,
397-         * args ,
398-         ** kwargs ,  # noqa: ANN001, ANN002, ANN003 
408+         cassette : VCRCassette ,
409+         real_send : Callable [..., Awaitable [httpx .Response ]],
410+         client : httpx .AsyncClient ,
411+         request : httpx .Request ,
412+         ** kwargs : object ,
399413    ) ->  httpx .Response :
414+         # VCR expects args tuple, so reconstruct it for original_shared_send 
415+         args  =  (client , request )
400416        vcr_request , response  =  original_shared_send (
401417            cassette , real_send , * args , ** kwargs 
402418        )
403-         client  =  args [0 ]
404-         real_request  =  args [1 ] if  len (args ) >  1  else  None 
419+         real_request : httpx .Request  |  None  =  request 
405420
406421        if  response  is  not None :
407422            client .cookies .extract_cookies (response )
0 commit comments