jroachgolf84 commented on code in PR #69382:
URL: https://github.com/apache/airflow/pull/69382#discussion_r3610487922


##########
providers/tableau/src/airflow/providers/tableau/operators/tableau.py:
##########
@@ -75,6 +82,11 @@ class TableauOperator(BaseOperator):
         when ``exponential_backoff`` is enabled. ``None`` leaves the growth 
uncapped.
     :param incremental_refresh: Whether to perform an incremental refresh 
instead of a full refresh.
         Only applies to datasource and workbook refresh operations. Defaults 
to False (full refresh).
+    :param skip_on_conflict: When ``True``, treat a Tableau ``409093 Resource 
Conflict`` error

Review Comment:
   Quick question from my side. So, the behavior would be something like this:
   
   - Task begins to execute
   - Immediately, a `409093 Resource Conflict` is raised
   - Rather than failing, the Task is skipped
   
   Is that correct?



##########
providers/tableau/tests/unit/tableau/operators/test_tableau.py:
##########
@@ -530,3 +535,122 @@ def 
test_blocking_refresh_forwards_wait_for_state_options(self, mock_tableau_hoo
             exponential_backoff=True,
             max_check_interval=120,
         )
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_skips_task(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict is turned into an 
AirflowSkipException
+        when skip_on_conflict=True.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            skip_on_conflict=True,
+            **self.kwargs,
+        )
+
+        with pytest.raises(AirflowSkipException):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_conflict_not_skipped_by_default(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict still fails the task when 
skip_on_conflict
+        is left at its default (False), preserving pre-existing behavior.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            **self.kwargs,
+        )
+
+        with pytest.raises(ServerResponseError):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_does_not_swallow_other_server_errors(self, 
mock_tableau_hook):
+        """
+        Test that skip_on_conflict only catches the 409093 conflict code; other
+        ServerResponseErrors still fail the task.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "500000", "Internal Server Error", "Something else went wrong."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            skip_on_conflict=True,
+            **self.kwargs,
+        )
+
+        with pytest.raises(ServerResponseError):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_skips_task_run(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict from tasks.run is turned into an
+        AirflowSkipException when skip_on_conflict=True.
+        """
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.tasks.get_by_id = Mock(return_value=Mock())
+        mock_tableau_hook.server.tasks.run.side_effect = ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        kwargs = {**self.kwargs, "method": "run", "match_with": "id"}
+        operator = TableauOperator(find="task-abc", resource="tasks", 
skip_on_conflict=True, **kwargs)
+
+        with pytest.raises(AirflowSkipException):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_task_run_conflict_not_skipped_by_default(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict from tasks.run still fails the 
task when

Review Comment:
   Does `tasks.run` refer to an Airflow Task? Or something else?



##########
providers/tableau/src/airflow/providers/tableau/operators/tableau.py:
##########
@@ -75,6 +82,11 @@ class TableauOperator(BaseOperator):
         when ``exponential_backoff`` is enabled. ``None`` leaves the growth 
uncapped.
     :param incremental_refresh: Whether to perform an incremental refresh 
instead of a full refresh.
         Only applies to datasource and workbook refresh operations. Defaults 
to False (full refresh).
+    :param skip_on_conflict: When ``True``, treat a Tableau ``409093 Resource 
Conflict`` error

Review Comment:
   Just thought I'd throw this out there. More generally, I'd also recommend to 
use something like a pool/Worker Queue to handle something like this (to ensure 
that only a single refresh for a resource can run). That's outside the scope of 
this PR, but thought I'd mention it.



##########
providers/tableau/tests/unit/tableau/operators/test_tableau.py:
##########
@@ -530,3 +535,122 @@ def 
test_blocking_refresh_forwards_wait_for_state_options(self, mock_tableau_hoo
             exponential_backoff=True,
             max_check_interval=120,
         )
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_skips_task(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict is turned into an 
AirflowSkipException
+        when skip_on_conflict=True.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            skip_on_conflict=True,
+            **self.kwargs,
+        )
+
+        with pytest.raises(AirflowSkipException):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_conflict_not_skipped_by_default(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict still fails the task when 
skip_on_conflict
+        is left at its default (False), preserving pre-existing behavior.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            **self.kwargs,
+        )
+
+        with pytest.raises(ServerResponseError):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_does_not_swallow_other_server_errors(self, 
mock_tableau_hook):
+        """
+        Test that skip_on_conflict only catches the 409093 conflict code; other
+        ServerResponseErrors still fail the task.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "500000", "Internal Server Error", "Something else went wrong."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            skip_on_conflict=True,
+            **self.kwargs,
+        )
+
+        with pytest.raises(ServerResponseError):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_skips_task_run(self, mock_tableau_hook):

Review Comment:
   What is the difference between this test and 
`test_skip_on_conflict_skips_task`?



##########
providers/tableau/tests/unit/tableau/operators/test_tableau.py:
##########
@@ -530,3 +535,122 @@ def 
test_blocking_refresh_forwards_wait_for_state_options(self, mock_tableau_hoo
             exponential_backoff=True,
             max_check_interval=120,
         )
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_skips_task(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict is turned into an 
AirflowSkipException
+        when skip_on_conflict=True.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            skip_on_conflict=True,
+            **self.kwargs,
+        )
+
+        with pytest.raises(AirflowSkipException):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_conflict_not_skipped_by_default(self, mock_tableau_hook):
+        """
+        Test that a 409093 Resource Conflict still fails the task when 
skip_on_conflict
+        is left at its default (False), preserving pre-existing behavior.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "409093", "Resource Conflict", "Job is already queued. Not queuing 
a duplicate."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            **self.kwargs,
+        )
+
+        with pytest.raises(ServerResponseError):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_does_not_swallow_other_server_errors(self, 
mock_tableau_hook):
+        """
+        Test that skip_on_conflict only catches the 409093 conflict code; other
+        ServerResponseErrors still fail the task.
+        """
+        mock_tableau_hook.get_all = Mock(return_value=self.mock_datasources)
+        mock_tableau_hook.return_value.__enter__ = 
Mock(return_value=mock_tableau_hook)
+        mock_tableau_hook.server.datasources.refresh.side_effect = 
ServerResponseError(
+            "500000", "Internal Server Error", "Something else went wrong."
+        )
+
+        operator = TableauOperator(
+            find="ds_2",
+            resource="datasources",
+            skip_on_conflict=True,
+            **self.kwargs,
+        )
+
+        with pytest.raises(ServerResponseError):
+            operator.execute(context={})
+
+    @patch("airflow.providers.tableau.operators.tableau.TableauHook")
+    def test_skip_on_conflict_skips_task_run(self, mock_tableau_hook):

Review Comment:
   It it's just the `run` method, can you denote that with a comment?



-- 
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