@@ -285,6 +285,20 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915
285285 ** kwargs ,
286286 )
287287
288+ self ._configure_sync_connection (http_auth , verify_certs , ssl_show_warn , ca_certs , client_cert , client_key )
289+ self .url_prefix , self .multi_dataset_url_prefix = _resolve_url_prefix (self .catalog , self .knowledge_base )
290+
291+ self .index_name : str | None = kwargs .get ("index" )
292+
293+ def _configure_sync_connection (
294+ self ,
295+ http_auth : Any ,
296+ verify_certs : bool ,
297+ ssl_show_warn : bool ,
298+ ca_certs : Any ,
299+ client_cert : Any ,
300+ client_key : Any ,
301+ ) -> None :
288302 if not self .http_compress :
289303 # Need to set this to 'None' otherwise Requests adds its own.
290304 self .session .headers ["accept-encoding" ] = None # type: ignore
@@ -313,9 +327,6 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915
313327
314328 if self .use_ssl and not verify_certs and ssl_show_warn :
315329 warnings .warn (f"Connecting to { self .host } using SSL with verify_certs=False is insecure." , stacklevel = 2 )
316- self .url_prefix , self .multi_dataset_url_prefix = _resolve_url_prefix (self .catalog , self .knowledge_base )
317-
318- self .index_name : str | None = kwargs .get ("index" )
319330
320331 def _tidy_url (self , url : str ) -> str :
321332 return url .replace ("%2F%7B" , "/" ).replace ("%7D%2F" , "/" ).replace ("%2F" , "/" )
@@ -576,40 +587,15 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915
576587 )
577588
578589 self .ssl_assert_fingerprint = ssl_assert_fingerprint
579- if self .use_ssl and ssl_context is None :
580- ssl_context = (
581- create_secure_ssl_context ()
582- if ssl_version is None
583- else ssl .SSLContext (ssl_version ) # NOSONAR (S4423): preserve caller-supplied protocol
584- )
585-
586- verify_certs , ssl_show_warn = _resolve_async_ssl_defaults (verify_certs , ssl_show_warn )
587-
588- if verify_certs :
589- ssl_context .verify_mode = ssl .CERT_REQUIRED
590- ssl_context .check_hostname = True
591- else :
592- ssl_context .check_hostname = False
593- ssl_context .verify_mode = ssl .CERT_NONE
594-
595- ca_certs = self .default_ca_certs () if ca_certs is None else ca_certs
596- if verify_certs :
597- if not ca_certs :
598- raise ImproperlyConfigured (
599- "Root certificates are missing for certificate "
600- "validation. Either pass them in using the ca_certs parameter or "
601- "install certifi to use it automatically."
602- )
603- _load_verify_locations (ssl_context , ca_certs )
604- elif ssl_show_warn :
605- warnings .warn (f"Connecting to { self .host } using SSL with verify_certs=False is insecure." , stacklevel = 2 )
606-
607- _validate_client_cert_path (client_cert , "client_cert" )
608- _validate_client_cert_path (client_key , "client_key" )
609- if client_cert and client_key :
610- ssl_context .load_cert_chain (client_cert , client_key )
611- elif client_cert :
612- ssl_context .load_cert_chain (client_cert )
590+ ssl_context = self ._build_async_ssl_context (
591+ ssl_context ,
592+ verify_certs ,
593+ ssl_show_warn ,
594+ ca_certs ,
595+ client_cert ,
596+ client_key ,
597+ ssl_version ,
598+ )
613599
614600 self .headers .setdefault ("connection" , "keep-alive" )
615601 self .loop = loop
@@ -624,6 +610,159 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0915
624610 self ._http_auth = http_auth
625611 self ._ssl_context = ssl_context
626612
613+ def _build_async_ssl_context (
614+ self ,
615+ ssl_context : Any ,
616+ verify_certs : Any ,
617+ ssl_show_warn : Any ,
618+ ca_certs : Any ,
619+ client_cert : Any ,
620+ client_key : Any ,
621+ ssl_version : Any ,
622+ ) -> Any :
623+ if not self .use_ssl or ssl_context is not None :
624+ return ssl_context
625+
626+ ssl_context = (
627+ create_secure_ssl_context ()
628+ if ssl_version is None
629+ else ssl .SSLContext (ssl_version ) # NOSONAR (S4423): preserve caller-supplied protocol
630+ )
631+ verify_certs , ssl_show_warn = _resolve_async_ssl_defaults (verify_certs , ssl_show_warn )
632+ self ._configure_async_ssl_context (ssl_context , verify_certs , ssl_show_warn , ca_certs )
633+ self ._load_async_client_cert_chain (ssl_context , client_cert , client_key )
634+ return ssl_context
635+
636+ def _configure_async_ssl_context (
637+ self ,
638+ ssl_context : ssl .SSLContext ,
639+ verify_certs : bool ,
640+ ssl_show_warn : bool ,
641+ ca_certs : Any ,
642+ ) -> None :
643+ if verify_certs :
644+ ssl_context .verify_mode = ssl .CERT_REQUIRED
645+ ssl_context .check_hostname = True
646+ else :
647+ ssl_context .check_hostname = False
648+ ssl_context .verify_mode = ssl .CERT_NONE
649+
650+ ca_certs = self .default_ca_certs () if ca_certs is None else ca_certs
651+ if verify_certs :
652+ if not ca_certs :
653+ raise ImproperlyConfigured (
654+ "Root certificates are missing for certificate "
655+ "validation. Either pass them in using the ca_certs parameter or "
656+ "install certifi to use it automatically."
657+ )
658+ _load_verify_locations (ssl_context , ca_certs )
659+ elif ssl_show_warn :
660+ warnings .warn (f"Connecting to { self .host } using SSL with verify_certs=False is insecure." , stacklevel = 2 )
661+
662+ @staticmethod
663+ def _load_async_client_cert_chain (
664+ ssl_context : ssl .SSLContext ,
665+ client_cert : Any ,
666+ client_key : Any ,
667+ ) -> None :
668+ _validate_client_cert_path (client_cert , "client_cert" )
669+ _validate_client_cert_path (client_key , "client_key" )
670+ if client_cert and client_key :
671+ ssl_context .load_cert_chain (client_cert , client_key )
672+ elif client_cert :
673+ ssl_context .load_cert_chain (client_cert )
674+
675+ def _prepare_async_request (
676+ self ,
677+ method : str ,
678+ url : str ,
679+ params : Mapping [str , Any ] | None ,
680+ body : bytes | None ,
681+ ) -> tuple [str , str , bytes | None , bytes | None , str ]:
682+ method = _normalize_method (method )
683+ url = self ._tidy_url (url )
684+ url = self ._make_url_valid (url , body )
685+ body = _prepare_embeddings_request_body (url , method , body , self .knowledge_base )
686+ orig_body = body
687+ query_string = urlencode (params ) if params else ""
688+ if query_string :
689+ url = f"{ url } ?{ query_string } "
690+ return method , url , body , orig_body , query_string
691+
692+ def _build_async_request_headers (
693+ self ,
694+ method : str ,
695+ url : str ,
696+ query_string : str ,
697+ body : bytes | None ,
698+ headers : Mapping [str , str ] | None ,
699+ ) -> tuple [dict [str , Any ], bytes | None , aiohttp .BasicAuth | None ]:
700+ req_headers = self .headers .copy ()
701+ if headers :
702+ req_headers .update (headers )
703+
704+ if self .http_compress and body :
705+ body = self ._gzip_compress (body )
706+ req_headers ["content-encoding" ] = "gzip"
707+
708+ auth = self ._http_auth if isinstance (self ._http_auth , aiohttp .BasicAuth ) else None
709+ if callable (self ._http_auth ):
710+ req_headers = {
711+ ** req_headers ,
712+ ** self ._http_auth (method , url , query_string , body ),
713+ }
714+ return req_headers , body , auth
715+
716+ def _raise_async_request_exception (
717+ self ,
718+ method : str ,
719+ url : str ,
720+ orig_body : bytes | None ,
721+ start : float ,
722+ exception : Exception ,
723+ ) -> None :
724+ self .log_request_fail (
725+ method ,
726+ str (url ),
727+ url ,
728+ orig_body ,
729+ self .loop .time () - start ,
730+ exception = exception ,
731+ )
732+ if isinstance (exception , aiohttp_exceptions .ServerFingerprintMismatch ):
733+ raise SSLError ("N/A" , str (exception ), exception ) from exception
734+ if isinstance (exception , (asyncio .TimeoutError , aiohttp_exceptions .ServerTimeoutError )):
735+ raise ConnectionTimeout ("TIMEOUT" , str (exception ), exception ) from exception
736+ raise ConnectionError ("N/A" , str (exception ), exception ) from exception
737+
738+ def _handle_async_response (
739+ self ,
740+ method : str ,
741+ url : str ,
742+ orig_body : bytes | None ,
743+ response : Any ,
744+ raw_data : str ,
745+ duration : float ,
746+ ignore : Collection [int ],
747+ ) -> None :
748+ warning_headers = response .headers .getall ("warning" , ())
749+ self ._raise_warnings (warning_headers )
750+
751+ raw_data_modified = str (_modify_post_response_langchain (raw_data ))
752+ if not (HTTP_OK <= response .status < HTTP_MULTIPLE_CHOICES ) and response .status not in ignore :
753+ self .log_request_fail (
754+ method ,
755+ str (url ),
756+ url ,
757+ orig_body ,
758+ duration ,
759+ status_code = response .status ,
760+ response = raw_data_modified ,
761+ )
762+ self ._raise_error (response .status , raw_data_modified )
763+
764+ self .log_request_success (method , str (url ), url , orig_body , response .status , raw_data_modified , duration )
765+
627766 def _tidy_url (self , url : str ) -> str :
628767 return url .replace ("%2F%7B" , "/" ).replace ("%7D%2F" , "/" ).replace ("%2F" , "/" )
629768
@@ -656,49 +795,16 @@ async def perform_request( # noqa: PLR0912, PLR0915
656795 await self ._create_aiohttp_session ()
657796 assert self .session is not None
658797
659- method = _normalize_method (method )
660-
661- url = self ._tidy_url (url )
662- url = self ._make_url_valid (url , body )
798+ method , url , body , orig_body , query_string = self ._prepare_async_request (method , url , params , body )
663799
664800 if _is_root_info_request (method , url , self .base_url ):
665801 return _mock_info_response ()
666802 skipped_response = _skip_unsupported_endpoint (url ) or _skip_multi_kb_write (self .knowledge_base , method , body )
667803 if skipped_response is not None :
668804 return skipped_response
669805
670- body = _prepare_embeddings_request_body (url , method , body , self .knowledge_base )
671- orig_body = body
672- query_string = urlencode (params ) if params else ""
673-
674- # Top-tier tip-toeing happening here. Basically
675- # because Pip's old resolver is bad and wipes out
676- # strict pins in favor of non-strict pins of extras
677- # our [async] extra overrides aiohttp's pin of
678- # yarl. yarl released breaking changes, aiohttp pinned
679- # defensively afterwards, but our users don't get
680- # that nice pin that aiohttp set. :( So to play around
681- # this super-defensively we try to import yarl, if we can't
682- # then we pass a string into ClientSession.request() instead.
683- if query_string :
684- url = f"{ url } ?{ query_string } "
685-
686806 request_timeout = aiohttp .ClientTimeout (total = timeout if timeout is not None else self .timeout )
687-
688- req_headers = self .headers .copy ()
689- if headers :
690- req_headers .update (headers )
691-
692- if self .http_compress and body :
693- body = self ._gzip_compress (body )
694- req_headers ["content-encoding" ] = "gzip"
695-
696- auth = self ._http_auth if isinstance (self ._http_auth , aiohttp .BasicAuth ) else None
697- if callable (self ._http_auth ):
698- req_headers = {
699- ** req_headers ,
700- ** self ._http_auth (method , url , query_string , body ),
701- }
807+ req_headers , body , auth = self ._build_async_request_headers (method , url , query_string , body , headers )
702808
703809 start = self .loop .time ()
704810 timeout_obj : ClientTimeout | None = ClientTimeout (total = timeout ) if isinstance (timeout , (int , float )) else None
@@ -719,41 +825,9 @@ async def perform_request( # noqa: PLR0912, PLR0915
719825 except reraise_exceptions :
720826 raise
721827 except Exception as e : # noqa: BLE001
722- self .log_request_fail (
723- method ,
724- str (url ),
725- url ,
726- orig_body ,
727- self .loop .time () - start ,
728- exception = e ,
729- )
730- if isinstance (e , aiohttp_exceptions .ServerFingerprintMismatch ):
731- raise SSLError ("N/A" , str (e ), e ) from e
732- if isinstance (e , (asyncio .TimeoutError , aiohttp_exceptions .ServerTimeoutError )):
733- raise ConnectionTimeout ("TIMEOUT" , str (e ), e ) from e
734- raise ConnectionError ("N/A" , str (e ), e ) from e
735-
736- # raise warnings if any from the 'Warnings' header.
737- warning_headers = response .headers .getall ("warning" , ())
738- self ._raise_warnings (warning_headers )
739-
740- raw_data_modified = str (_modify_post_response_langchain (raw_data ))
741-
742- # raise errors based on http status codes, let the client handle those if needed
743- if not (HTTP_OK <= response .status < HTTP_MULTIPLE_CHOICES ) and response .status not in ignore :
744- self .log_request_fail (
745- method ,
746- str (url ),
747- url ,
748- orig_body ,
749- duration ,
750- status_code = response .status ,
751- response = raw_data_modified ,
752- )
753- self ._raise_error (response .status , raw_data_modified )
754-
755- self .log_request_success (method , str (url ), url , orig_body , response .status , raw_data_modified , duration )
828+ self ._raise_async_request_exception (method , url , orig_body , start , e )
756829
830+ self ._handle_async_response (method , url , orig_body , response , raw_data , duration , ignore )
757831 return response .status , response .headers , raw_data
758832
759833 async def close (self ) -> Any :
0 commit comments