phanikumv commented on code in PR #65499:
URL: https://github.com/apache/airflow/pull/65499#discussion_r3110075267


##########
providers/google/src/airflow/providers/google/cloud/operators/bigquery.py:
##########
@@ -2563,3 +2564,494 @@ def on_kill(self) -> None:
             )
         else:
             self.log.info("Skipping to cancel job: %s:%s.%s", self.project_id, 
self.location, self.job_id)
+
+
+class BigQueryCreateRoutineOperator(GoogleCloudBaseOperator):
+    """
+    Create a BigQuery routine (UDF, stored procedure, table-valued function, 
or aggregate).
+
+    The routine is defined by a ``Routine`` resource as documented at
+    https://cloud.google.com/bigquery/docs/reference/rest/v2/routines#Routine. 
The full resource
+    may be passed via ``routine_resource``, or individual fields can be passed 
as keyword
+    arguments, which are merged into ``routine_resource`` at execute time.
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:BigQueryCreateRoutineOperator`
+
+    :param dataset_id: The dataset that will own the routine.
+    :param routine_id: The identifier of the routine to create.
+    :param routine_resource: The routine resource as a dict or
+        :class:`~google.cloud.bigquery.routine.Routine`. If omitted, the 
resource is assembled
+        from the individual keyword arguments below.
+    :param project_id: Optional. The project that owns the dataset. Falls back 
to the connection's
+        default.
+    :param routine_type: Optional. One of ``"SCALAR_FUNCTION"``, 
``"PROCEDURE"``,
+        ``"TABLE_VALUED_FUNCTION"``, ``"AGGREGATE_FUNCTION"``.
+    :param language: Optional. ``"SQL"`` or ``"JAVASCRIPT"``.
+    :param definition_body: Optional. The body of the routine (SQL or 
JavaScript).
+    :param arguments: Optional. Sequence of argument dicts in the Google API 
representation
+        (see the ``Routine.Argument`` schema).
+    :param return_type: Optional. The return type of the routine as a 
``StandardSqlDataType``
+        dict.
+    :param return_table_type: Optional. The return table type for table-valued 
functions as a
+        ``StandardSqlTableType`` dict.
+    :param imported_libraries: Optional. URIs of Cloud Storage libraries to 
import (JavaScript
+        UDFs only).
+    :param determinism_level: Optional. Determinism level for the routine.
+    :param security_mode: Optional. Security mode (``"DEFINER"`` or 
``"INVOKER"``).
+    :param data_governance_type: Optional. Data governance type for the 
routine.
+    :param description: Optional. Description of the routine.
+    :param remote_function_options: Optional. Options for remote functions.
+    :param spark_options: Optional. Options for Spark stored procedures.
+    :param if_exists: What to do if a routine with the same identifier already 
exists.
+        ``"fail"`` (default) raises, ``"skip"`` returns the existing routine, 
``"replace"``
+        deletes the existing routine and creates the new one.
+    :param gcp_conn_id: The connection ID used to connect to Google Cloud.
+    :param location: The location of the BigQuery dataset.
+    :param impersonation_chain: Optional service account to impersonate using 
short-term
+        credentials, or chained list of accounts.
+    :param retry: A retry object used to retry requests.
+    :param timeout: The amount of time, in seconds, to wait for the request.
+    """
+
+    template_fields: Sequence[str] = (
+        "project_id",
+        "dataset_id",
+        "routine_id",
+        "definition_body",
+        "arguments",
+        "routine_resource",
+        "gcp_conn_id",
+        "impersonation_chain",
+    )
+    template_fields_renderers = {"routine_resource": "json", "arguments": 
"json"}
+    template_ext: Sequence[str] = (".sql",)
+    ui_color = BigQueryUIColors.TABLE.value
+
+    def __init__(
+        self,
+        *,
+        dataset_id: str,
+        routine_id: str,
+        routine_resource: dict[str, Any] | Routine | None = None,
+        project_id: str = PROVIDE_PROJECT_ID,
+        routine_type: str | None = None,
+        language: str | None = None,
+        definition_body: str | None = None,
+        arguments: Sequence[dict[str, Any]] | None = None,
+        return_type: dict[str, Any] | None = None,
+        return_table_type: dict[str, Any] | None = None,
+        imported_libraries: Sequence[str] | None = None,
+        determinism_level: str | None = None,
+        security_mode: str | None = None,
+        data_governance_type: str | None = None,
+        description: str | None = None,
+        remote_function_options: dict[str, Any] | None = None,
+        spark_options: dict[str, Any] | None = None,
+        if_exists: str = "fail",
+        gcp_conn_id: str = "google_cloud_default",
+        location: str | None = None,
+        impersonation_chain: str | Sequence[str] | None = None,
+        retry: Retry = DEFAULT_RETRY,
+        timeout: float | None = None,
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+        if if_exists not in {"fail", "skip", "replace"}:
+            raise ValueError(f"`if_exists` must be one of 'fail', 'skip', 
'replace'; got {if_exists!r}")
+        self.project_id = project_id
+        self.dataset_id = dataset_id
+        self.routine_id = routine_id
+        self.routine_resource = routine_resource
+        self.routine_type = routine_type
+        self.language = language
+        self.definition_body = definition_body
+        self.arguments = arguments
+        self.return_type = return_type
+        self.return_table_type = return_table_type
+        self.imported_libraries = imported_libraries
+        self.determinism_level = determinism_level
+        self.security_mode = security_mode
+        self.data_governance_type = data_governance_type
+        self.description = description
+        self.remote_function_options = remote_function_options
+        self.spark_options = spark_options
+        self.if_exists = if_exists
+        self.gcp_conn_id = gcp_conn_id
+        self.location = location
+        self.impersonation_chain = impersonation_chain
+        self.retry = retry
+        self.timeout = timeout
+
+    def _build_resource(self) -> dict[str, Any]:
+        if isinstance(self.routine_resource, Routine):
+            resource = self.routine_resource.to_api_repr()
+        elif self.routine_resource is not None:
+            resource = dict(self.routine_resource)
+        else:
+            resource = {}
+
+        field_map = {
+            "routineType": self.routine_type,
+            "language": self.language,
+            "definitionBody": self.definition_body,
+            "arguments": list(self.arguments) if self.arguments is not None 
else None,
+            "returnType": self.return_type,
+            "returnTableType": self.return_table_type,
+            "importedLibraries": (
+                list(self.imported_libraries) if self.imported_libraries is 
not None else None
+            ),
+            "determinismLevel": self.determinism_level,
+            "securityMode": self.security_mode,
+            "dataGovernanceType": self.data_governance_type,
+            "description": self.description,
+            "remoteFunctionOptions": self.remote_function_options,
+            "sparkOptions": self.spark_options,
+        }
+        for key, value in field_map.items():
+            if value is not None:
+                resource.setdefault(key, value)
+        return resource
+
+    def execute(self, context: Context) -> dict[str, Any]:
+        hook = BigQueryHook(
+            gcp_conn_id=self.gcp_conn_id,
+            location=self.location,
+            impersonation_chain=self.impersonation_chain,
+        )
+        resource = self._build_resource()
+        self.log.info(
+            "Creating routine %s.%s.%s (if_exists=%s)",
+            self.project_id or hook.project_id,
+            self.dataset_id,
+            self.routine_id,
+            self.if_exists,
+        )
+        routine = hook.create_routine(
+            routine=resource,
+            dataset_id=self.dataset_id,
+            routine_id=self.routine_id,
+            project_id=self.project_id,
+            if_exists=self.if_exists,
+            retry=self.retry,
+            timeout=self.timeout,
+        )
+        return routine.to_api_repr()
+
+
+class BigQueryUpdateRoutineOperator(GoogleCloudBaseOperator):
+    """
+    Patch selected fields of an existing BigQuery routine.
+
+    Only the fields listed in ``fields`` are updated. Any field listed but 
left unset in
+    ``routine_resource`` is cleared on the server.
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:BigQueryUpdateRoutineOperator`
+
+    :param dataset_id: The dataset that owns the routine.
+    :param routine_id: The identifier of the routine to update.
+    :param routine_resource: The routine resource (dict or
+        :class:`~google.cloud.bigquery.routine.Routine`) with the new values 
for the fields
+        listed in ``fields``.
+    :param fields: Properties to update (e.g. ``["body", "description"]``).
+    :param project_id: Optional. The project that owns the dataset.
+    :param gcp_conn_id: The connection ID used to connect to Google Cloud.
+    :param location: The location of the BigQuery dataset.
+    :param impersonation_chain: Optional service account to impersonate.
+    :param retry: A retry object used to retry requests.
+    :param timeout: The amount of time, in seconds, to wait for the request.
+    """
+
+    template_fields: Sequence[str] = (
+        "project_id",
+        "dataset_id",
+        "routine_id",
+        "routine_resource",
+        "gcp_conn_id",
+        "impersonation_chain",
+    )
+    template_fields_renderers = {"routine_resource": "json"}
+    template_ext: Sequence[str] = (".json", ".sql")
+    ui_color = BigQueryUIColors.TABLE.value
+
+    def __init__(
+        self,
+        *,
+        dataset_id: str,
+        routine_id: str,
+        routine_resource: dict[str, Any] | Routine,
+        fields: Sequence[str],
+        project_id: str = PROVIDE_PROJECT_ID,
+        gcp_conn_id: str = "google_cloud_default",
+        location: str | None = None,
+        impersonation_chain: str | Sequence[str] | None = None,
+        retry: Retry = DEFAULT_RETRY,
+        timeout: float | None = None,
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+        if not fields:
+            raise ValueError("`fields` must be a non-empty sequence of routine 
properties to update.")
+        self.project_id = project_id
+        self.dataset_id = dataset_id
+        self.routine_id = routine_id
+        self.routine_resource = routine_resource
+        self.fields = list(fields)
+        self.gcp_conn_id = gcp_conn_id
+        self.location = location
+        self.impersonation_chain = impersonation_chain
+        self.retry = retry
+        self.timeout = timeout
+
+    def execute(self, context: Context) -> dict[str, Any]:
+        hook = BigQueryHook(
+            gcp_conn_id=self.gcp_conn_id,
+            location=self.location,
+            impersonation_chain=self.impersonation_chain,
+        )
+        self.log.info(
+            "Updating routine %s.%s.%s (fields=%s)",
+            self.project_id or hook.project_id,
+            self.dataset_id,
+            self.routine_id,
+            self.fields,
+        )
+        routine = hook.update_routine(
+            routine=self.routine_resource,
+            fields=self.fields,
+            dataset_id=self.dataset_id,
+            routine_id=self.routine_id,
+            project_id=self.project_id,
+            retry=self.retry,
+            timeout=self.timeout,
+        )
+        return routine.to_api_repr()
+
+
+class BigQueryDeleteRoutineOperator(GoogleCloudBaseOperator):
+    """
+    Delete a BigQuery routine.
+
+    .. seealso::
+        For more information on how to use this operator, take a look at the 
guide:
+        :ref:`howto/operator:BigQueryDeleteRoutineOperator`
+
+    :param dataset_id: The dataset that owns the routine.
+    :param routine_id: The identifier of the routine to delete.
+    :param project_id: Optional. The project that owns the dataset.
+    :param ignore_if_missing: If ``True``, do not fail when the routine does 
not exist.
+        Defaults to ``False``.
+    :param gcp_conn_id: The connection ID used to connect to Google Cloud.
+    :param location: The location of the BigQuery dataset.
+    :param impersonation_chain: Optional service account to impersonate.
+    :param retry: A retry object used to retry requests.
+    :param timeout: The amount of time, in seconds, to wait for the request.
+    """
+
+    template_fields: Sequence[str] = (
+        "project_id",
+        "dataset_id",
+        "routine_id",
+        "gcp_conn_id",
+        "impersonation_chain",
+    )
+    ui_color = BigQueryUIColors.TABLE.value
+
+    def __init__(
+        self,
+        *,
+        dataset_id: str,
+        routine_id: str,
+        project_id: str = PROVIDE_PROJECT_ID,

Review Comment:
   The hook and operator have **opposite defaults** for the "ignore missing" 
behavior:
   
   - `BigQueryHook.delete_routine(not_found_ok=True)` — silently ignores 404 by 
default
   - `BigQueryDeleteRoutineOperator(ignore_if_missing=False)` — raises on 404 
by default
   
   This inconsistency means users who call the hook directly get different 
behavior than those using the operator with default settings. The operator's 
`ignore_if_missing=False` is the safer default (explicit, fails loudly), and 
the hook should match it with `not_found_ok=False` — or at minimum the 
discrepancy should be clearly documented in both docstrings.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to