3232from datacustomcode .file .path .default import DefaultFindFilePath
3333from datacustomcode .io .reader .base import BaseDataCloudReader
3434from datacustomcode .llm_gateway_config import spark_llm_gateway_config
35+ from datacustomcode .named_credential_config import spark_named_credential_config
3536from datacustomcode .spark .default import DefaultSparkSessionProvider
3637
3738if TYPE_CHECKING :
4950 from datacustomcode .io .reader .base import BaseDataCloudReader
5051 from datacustomcode .io .writer .base import BaseDataCloudWriter , WriteMode
5152 from datacustomcode .llm_gateway .spark_base import SparkLLMGateway
53+ from datacustomcode .named_credential .spark_base import SparkNamedCredential
54+ from datacustomcode .named_credential .types .http_request import HTTPRequest
55+ from datacustomcode .named_credential .types .http_response import HTTPResponse
5256 from datacustomcode .spark .base import BaseSparkSessionProvider
5357
5458
@@ -160,6 +164,21 @@ def _build_spark_einstein_predictions() -> "SparkEinsteinPredictions":
160164 return cfg .to_object ()
161165
162166
167+ def _build_spark_named_credential () -> "SparkNamedCredential" :
168+ """Instantiate the SDK-configured :class:`SparkNamedCredential`.
169+
170+ Raises:
171+ RuntimeError: If no ``spark_named_credential_config`` has been loaded.
172+ """
173+ cfg = spark_named_credential_config .spark_named_credential_config
174+ if cfg is None :
175+ raise RuntimeError (
176+ "spark_named_credential_config is not configured. Add a "
177+ "'spark_named_credential_config' section to config.yaml."
178+ )
179+ return cfg .to_object ()
180+
181+
163182def einstein_predict_col (
164183 model_api_name : str ,
165184 prediction_type : "PredictionType" ,
@@ -209,6 +228,40 @@ def einstein_predict_col(
209228 )
210229
211230
231+ def named_credential_request_col (
232+ request : "HTTPRequest" ,
233+ body : Optional ["Column" ] = None ,
234+ ) -> "Column" :
235+ """Build a Spark Column that makes one Named Credential callout per row.
236+
237+ The endpoint, method, and headers are fixed for the call (taken from
238+ ``request``); only ``body`` varies per row. Use this instead of
239+ :meth:`Client.named_credential_request` when the callout runs across a
240+ DataFrame so each row is dispatched independently rather than one-shot on
241+ the driver.
242+
243+ The returned Column yields a struct ``{status, response, error_code,
244+ error_message}`` for each row. ``response`` is itself a struct
245+ ``{status_code, body, headers}``. Use ``[...]`` to pick a field, e.g.
246+ ``named_credential_request_col(...)["response"]["status_code"]``. A transport
247+ failure sets ``status`` to ``ERROR`` and populates ``error_message`` (a non-2xx
248+ HTTP response is still ``SUCCESS`` with its code in ``response.status_code``),
249+ so a single bad row does not abort the whole Spark job.
250+
251+ Args:
252+ request: The callout template — its symbolic reference, method, and
253+ headers are applied to every row.
254+ body: Optional per-row ``Column`` holding the request body as a
255+ string (or null for no body).
256+
257+ Returns:
258+ A Spark ``Column`` of ``StructType`` with fields ``status``,
259+ ``response``, ``error_code``, and ``error_message``.
260+ """
261+ named_credential = Client ()._get_spark_named_credential ()
262+ return named_credential .request_col (request , body = body )
263+
264+
212265class DataCloudObjectType (Enum ):
213266 DLO = "dlo"
214267 DMO = "dmo"
@@ -266,6 +319,14 @@ class _BaseClient:
266319 spark_llm_gateway: Optional custom :class:`SparkLLMGateway`.
267320 spark_einstein_predictions: Optional custom
268321 :class:`SparkEinsteinPredictions`.
322+ spark_named_credential: Optional custom :class:`SparkNamedCredential`.
323+
324+ Example:
325+ >>> client = Client()
326+ >>> file_path = client.find_file_path("data.csv")
327+ >>> dlo = client.read_dlo("my_dlo")
328+ >>> client.write_to_dmo("my_dmo", dlo)
329+ >>> answer = client.llm_gateway_generate_text("Generate a greeting message")
269330 """
270331
271332 # Each concrete subclass gets its own ``_instance`` slot: reads fall through
@@ -285,6 +346,7 @@ class _BaseClient:
285346 _file : DefaultFindFilePath
286347 _spark_llm_gateway : Optional [SparkLLMGateway ]
287348 _spark_einstein_predictions : Optional [SparkEinsteinPredictions ]
349+ _spark_named_credential : Optional [SparkNamedCredential ]
288350 _data_layer_history : dict [DataCloudObjectType , set [str ]]
289351 _code_type : str
290352
@@ -295,13 +357,15 @@ def __new__(
295357 spark_provider : Optional [BaseSparkSessionProvider ] = None ,
296358 spark_llm_gateway : Optional [SparkLLMGateway ] = None ,
297359 spark_einstein_predictions : Optional [SparkEinsteinPredictions ] = None ,
360+ spark_named_credential : Optional [SparkNamedCredential ] = None ,
298361 code_type : str = "script" ,
299362 ) -> _ClientT :
300363
301364 if cls ._instance is None :
302365 instance = super ().__new__ (cls )
303366 instance ._spark_llm_gateway = spark_llm_gateway
304367 instance ._spark_einstein_predictions = spark_einstein_predictions
368+ instance ._spark_named_credential = spark_named_credential
305369 # Initialize Readers and Writers from config
306370 # and/or provided reader and writer
307371 if reader is None or writer is None :
@@ -474,6 +538,41 @@ def _get_spark_einstein_predictions(self) -> SparkEinsteinPredictions:
474538 self ._spark_einstein_predictions = _build_spark_einstein_predictions ()
475539 return self ._spark_einstein_predictions
476540
541+ def named_credential_request (
542+ self ,
543+ request : "HTTPRequest" ,
544+ body : Optional [str ] = None ,
545+ ) -> "HTTPResponse" :
546+ """Issue a one-shot Named Credential external callout. This is the
547+ scalar counterpart to :func:`named_credential_request_col`: it runs
548+ **once** on the driver — not per row. Use the column helper method
549+ instead when you want to fan a callout out across every row of a
550+ DataFrame.
551+
552+ Example:
553+
554+ >>> from datacustomcode.named_credential.types.http_request_builder \\
555+ ... import HTTPRequestBuilder
556+ >>> request = (
557+ ... HTTPRequestBuilder().set_url("callout:NC/search").build()
558+ ... )
559+ >>> response = Client().named_credential_request(request)
560+
561+ Args:
562+ request: The callout request
563+ body: Optional request body. Set the ``Content-Type`` header to
564+ match the format; the SDK does not assume or inject one.
565+
566+ Returns:
567+ The external service's response.
568+ """
569+ return self ._get_spark_named_credential ().request (request , body = body )
570+
571+ def _get_spark_named_credential (self ) -> SparkNamedCredential :
572+ if self ._spark_named_credential is None :
573+ self ._spark_named_credential = _build_spark_named_credential ()
574+ return self ._spark_named_credential
575+
477576 def _validate_data_layer_history_does_not_contain (
478577 self , data_cloud_object_type : DataCloudObjectType
479578 ) -> None :
0 commit comments