|
16 | 16 | import json |
17 | 17 | from typing import Any |
18 | 18 |
|
| 19 | +from google.auth.transport import mtls |
| 20 | +from google.auth.transport import requests as auth_requests |
19 | 21 | import requests |
20 | 22 |
|
| 23 | +from google import auth |
| 24 | + |
| 25 | +from ..utils import _mtls_utils |
| 26 | + |
| 27 | +_GDA_DEFAULT_TEMPLATE = "https://geminidataanalytics.googleapis.com" |
| 28 | +_GDA_MTLS_TEMPLATE = "https://geminidataanalytics.mtls.googleapis.com" |
| 29 | + |
| 30 | + |
| 31 | +def get_gda_endpoint() -> str: |
| 32 | + """Returns the GDA API endpoint based on mTLS configuration.""" |
| 33 | + return _mtls_utils.get_api_endpoint( |
| 34 | + location="", |
| 35 | + default_template=_GDA_DEFAULT_TEMPLATE, |
| 36 | + mtls_template=_GDA_MTLS_TEMPLATE, |
| 37 | + ) |
| 38 | + |
| 39 | + |
| 40 | +def get_gda_session( |
| 41 | + credentials: auth.credentials.Credentials, |
| 42 | +) -> tuple[requests.Session, str]: |
| 43 | + """Creates an AuthorizedSession and returns it with the correct endpoint. |
| 44 | +
|
| 45 | + Args: |
| 46 | + credentials: The credentials to use for the request. |
| 47 | +
|
| 48 | + Returns: |
| 49 | + A tuple containing the authorized requests Session and the GDA endpoint. |
| 50 | +
|
| 51 | + Raises: |
| 52 | + ValueError: If the mTLS endpoint is selected but the client certificate |
| 53 | + is disabled. |
| 54 | + """ |
| 55 | + session = auth_requests.AuthorizedSession(credentials=credentials) # type: ignore[no-untyped-call] |
| 56 | + endpoint = get_gda_endpoint() |
| 57 | + |
| 58 | + if endpoint == _GDA_MTLS_TEMPLATE: |
| 59 | + if not mtls.has_default_client_cert_source(): # type: ignore[no-untyped-call] |
| 60 | + raise ValueError( |
| 61 | + "mTLS endpoint is selected, but client certificate is not" |
| 62 | + " provisioned." |
| 63 | + ) |
| 64 | + session.configure_mtls_channel() # type: ignore[no-untyped-call] |
| 65 | + |
| 66 | + return session, endpoint |
| 67 | + |
21 | 68 |
|
22 | 69 | def get_stream( |
| 70 | + session: requests.Session, |
23 | 71 | url: str, |
24 | 72 | ca_payload: dict[str, Any], |
25 | 73 | headers: dict[str, str], |
26 | 74 | max_query_result_rows: int, |
27 | 75 | ) -> list[dict[str, Any]]: |
28 | 76 | """Sends a JSON request to a streaming API and returns a list of messages.""" |
29 | | - with requests.Session() as s: |
30 | | - accumulator = "" |
31 | | - messages = [] |
32 | | - data_msg_idx = -1 |
33 | | - |
34 | | - with s.post(url, json=ca_payload, headers=headers, stream=True) as resp: |
35 | | - resp.raise_for_status() |
36 | | - for line in resp.iter_lines(): |
37 | | - if not line: |
38 | | - continue |
39 | | - |
40 | | - decoded_line = line.decode("utf-8") |
41 | | - |
42 | | - if decoded_line == "[{": |
43 | | - accumulator = "{" |
44 | | - elif decoded_line == "}]": |
45 | | - accumulator += "}" |
46 | | - elif decoded_line == ",": |
47 | | - continue |
48 | | - else: |
49 | | - accumulator += decoded_line |
50 | | - |
51 | | - try: |
52 | | - data_json = json.loads(accumulator) |
53 | | - except ValueError: |
54 | | - continue |
55 | | - |
56 | | - accumulator = "" |
57 | | - |
58 | | - if not isinstance(data_json, dict): |
59 | | - messages.append(data_json) |
60 | | - continue |
61 | | - |
62 | | - processed_msg = None |
63 | | - data_result = _extract_data_result(data_json) |
64 | | - if data_result is not None: |
65 | | - processed_msg = _format_data_retrieved( |
66 | | - data_result, max_query_result_rows |
67 | | - ) |
68 | | - if data_msg_idx >= 0: |
69 | | - messages[data_msg_idx] = { |
70 | | - "Data Retrieved": "Intermediate result omitted" |
71 | | - } |
72 | | - data_msg_idx = len(messages) |
73 | | - elif isinstance(data_json.get("systemMessage"), dict): |
74 | | - processed_msg = data_json["systemMessage"] |
75 | | - else: |
76 | | - processed_msg = data_json |
77 | | - |
78 | | - if processed_msg is not None: |
79 | | - messages.append(processed_msg) |
80 | | - |
81 | | - return messages |
| 77 | + accumulator = "" |
| 78 | + messages = [] |
| 79 | + data_msg_idx = -1 |
| 80 | + |
| 81 | + with session.post(url, json=ca_payload, headers=headers, stream=True) as resp: |
| 82 | + resp.raise_for_status() |
| 83 | + for line in resp.iter_lines(): |
| 84 | + if not line: |
| 85 | + continue |
| 86 | + |
| 87 | + decoded_line = line.decode("utf-8") |
| 88 | + |
| 89 | + if decoded_line == "[{": |
| 90 | + accumulator = "{" |
| 91 | + elif decoded_line == "}]": |
| 92 | + accumulator += "}" |
| 93 | + elif decoded_line == ",": |
| 94 | + continue |
| 95 | + else: |
| 96 | + accumulator += decoded_line |
| 97 | + |
| 98 | + try: |
| 99 | + data_json = json.loads(accumulator) |
| 100 | + except ValueError: |
| 101 | + continue |
| 102 | + |
| 103 | + accumulator = "" |
| 104 | + |
| 105 | + if not isinstance(data_json, dict): |
| 106 | + messages.append(data_json) |
| 107 | + continue |
| 108 | + |
| 109 | + processed_msg = None |
| 110 | + data_result = _extract_data_result(data_json) |
| 111 | + if data_result is not None: |
| 112 | + processed_msg = _format_data_retrieved( |
| 113 | + data_result, max_query_result_rows |
| 114 | + ) |
| 115 | + if data_msg_idx >= 0: |
| 116 | + messages[data_msg_idx] = { |
| 117 | + "Data Retrieved": "Intermediate result omitted" |
| 118 | + } |
| 119 | + data_msg_idx = len(messages) |
| 120 | + elif isinstance(data_json.get("systemMessage"), dict): |
| 121 | + processed_msg = data_json["systemMessage"] |
| 122 | + else: |
| 123 | + processed_msg = data_json |
| 124 | + |
| 125 | + if processed_msg is not None: |
| 126 | + messages.append(processed_msg) |
| 127 | + |
| 128 | + return messages |
82 | 129 |
|
83 | 130 |
|
84 | 131 | def _extract_data_result(msg: dict[str, Any]) -> dict[str, Any] | None: |
|
0 commit comments