You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by sh...@apache.org on 2026/07/26 05:55:44 UTC
(airflow) branch main updated: Move template-field validation out of AnthropicAgentSessionOperator __init__ (#70432)
This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 9b72aed307d Move template-field validation out of AnthropicAgentSessionOperator __init__ (#70432)
9b72aed307d is described below
commit 9b72aed307d250a138ad08c6c13a3562edba7a40
Author: Bramhanand Lingala <br...@gmail.com>
AuthorDate: Sun Jul 26 01:55:36 2026 -0400
Move template-field validation out of AnthropicAgentSessionOperator __init__ (#70432)
---
.../airflow/providers/anthropic/operators/agent.py | 8 ++--
.../tests/unit/anthropic/operators/test_agent.py | 44 +++++++++++++++-------
.../ci/prek/validate_operators_init_exemptions.txt | 1 -
3 files changed, 35 insertions(+), 18 deletions(-)
diff --git a/providers/anthropic/src/airflow/providers/anthropic/operators/agent.py b/providers/anthropic/src/airflow/providers/anthropic/operators/agent.py
index 22e2860c64d..e829ab0de6a 100644
--- a/providers/anthropic/src/airflow/providers/anthropic/operators/agent.py
+++ b/providers/anthropic/src/airflow/providers/anthropic/operators/agent.py
@@ -103,10 +103,6 @@ class AnthropicAgentSessionOperator(BaseOperator):
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
- if (message is None) == (outcome is None):
- raise ValueError("Provide exactly one of 'message' or 'outcome'.")
- if outcome is not None and not {"description", "rubric"} <= outcome.keys():
- raise ValueError("'outcome' must include both 'description' and 'rubric'.")
self.agent_id = agent_id
self.environment_id = environment_id
self.message = message
@@ -126,6 +122,10 @@ class AnthropicAgentSessionOperator(BaseOperator):
return AnthropicHook(conn_id=self.conn_id)
def execute(self, context: Context) -> str | None:
+ if (self.message is None) == (self.outcome is None):
+ raise ValueError("Provide exactly one of 'message' or 'outcome'.")
+ if self.outcome is not None and not {"description", "rubric"} <= self.outcome.keys():
+ raise ValueError("'outcome' must include both 'description' and 'rubric'.")
create_kwargs: dict[str, Any] = dict(self.session_kwargs)
if self.vault_ids:
create_kwargs["vault_ids"] = self.vault_ids
diff --git a/providers/anthropic/tests/unit/anthropic/operators/test_agent.py b/providers/anthropic/tests/unit/anthropic/operators/test_agent.py
index 3c60e3e0e80..3c4f595ad51 100644
--- a/providers/anthropic/tests/unit/anthropic/operators/test_agent.py
+++ b/providers/anthropic/tests/unit/anthropic/operators/test_agent.py
@@ -38,28 +38,46 @@ def _context():
def test_requires_exactly_one_of_message_or_outcome():
+ op = AnthropicAgentSessionOperator(task_id="a", agent_id="ag", environment_id="env")
with pytest.raises(ValueError, match="exactly one"):
- AnthropicAgentSessionOperator(task_id="a", agent_id="ag", environment_id="env")
+ op.execute(_context())
+
+ op = AnthropicAgentSessionOperator(
+ task_id="a", agent_id="ag", environment_id="env", message="hi", outcome={"description": "x"}
+ )
with pytest.raises(ValueError, match="exactly one"):
- AnthropicAgentSessionOperator(
- task_id="a", agent_id="ag", environment_id="env", message="hi", outcome={"description": "x"}
- )
+ op.execute(_context())
def test_outcome_requires_description_and_rubric():
# missing rubric
+ op = AnthropicAgentSessionOperator(
+ task_id="a", agent_id="ag", environment_id="env", outcome={"description": "x"}
+ )
with pytest.raises(ValueError, match="description.*rubric"):
- AnthropicAgentSessionOperator(
- task_id="a", agent_id="ag", environment_id="env", outcome={"description": "x"}
- )
+ op.execute(_context())
+
# missing description
+ op = AnthropicAgentSessionOperator(
+ task_id="a",
+ agent_id="ag",
+ environment_id="env",
+ outcome={"rubric": {"type": "text", "content": "c"}},
+ )
with pytest.raises(ValueError, match="description.*rubric"):
- AnthropicAgentSessionOperator(
- task_id="a",
- agent_id="ag",
- environment_id="env",
- outcome={"rubric": {"type": "text", "content": "c"}},
- )
+ op.execute(_context())
+
+
+def test_init_does_not_validate_message_or_outcome():
+ """Regression test: __init__ must not read template-field values (see #70296)."""
+ op = AnthropicAgentSessionOperator(task_id="a", agent_id="ag", environment_id="env")
+ assert op.message is None
+ assert op.outcome is None
+
+ op = AnthropicAgentSessionOperator(
+ task_id="a", agent_id="ag", environment_id="env", outcome={"description": "x"}
+ )
+ assert op.outcome == {"description": "x"}
class TestExecute:
diff --git a/scripts/ci/prek/validate_operators_init_exemptions.txt b/scripts/ci/prek/validate_operators_init_exemptions.txt
index 0890990686d..e27de0f6a8b 100644
--- a/scripts/ci/prek/validate_operators_init_exemptions.txt
+++ b/scripts/ci/prek/validate_operators_init_exemptions.txt
@@ -20,7 +20,6 @@ providers/amazon/src/airflow/providers/amazon/aws/operators/step_function.py::St
providers/amazon/src/airflow/providers/amazon/aws/transfers/base.py::AwsToAwsBaseOperator
providers/amazon/src/airflow/providers/amazon/aws/transfers/gcs_to_s3.py::GCSToS3Operator
providers/amazon/src/airflow/providers/amazon/aws/transfers/s3_to_redshift.py::S3ToRedshiftOperator
-providers/anthropic/src/airflow/providers/anthropic/operators/agent.py::AnthropicAgentSessionOperator
providers/cncf/kubernetes/src/airflow/providers/cncf/kubernetes/operators/pod.py::KubernetesPodOperator
providers/google/src/airflow/providers/google/cloud/operators/bigquery.py::BigQueryInsertJobOperator
providers/google/src/airflow/providers/google/cloud/operators/cloud_batch.py::CloudBatchSubmitJobOperator