FrankYang0529 opened a new pull request, #73081:
URL: https://github.com/apache/airflow/pull/73081
## Why
- `PythonVirtualenvOperator` uses a virtualenv built from `requirements`,
and `ExternalPythonOperator` uses the interpreter given in `python`. An `async
def` callable never reaches `execute_callable()`, so `requirements`, `python`,
and `venv_cache_path` are ignored.
- Either the callable runs with the worker's own packages and the task still
succeeds, or it fails with an `ImportError` that looks like a broken virtualenv.
## How
- `_BasePythonVirtualenvOperator.__init__` raises `ValueError` for async
callables. The Dag now fails at parse time.
## Verification
- Unit test: `uv run --frozen --project providers/standard pytest
providers/standard/tests/unit/standard/operators/test_python.py`
- Integration test:
1. Setup
```sh
export DEMO_DIR="${TMPDIR:-/tmp}/airflow-async-venv-demo"
export AIRFLOW_HOME="$DEMO_DIR/airflow-home"
export AIRFLOW__CORE__DAGS_FOLDER="$DEMO_DIR/dags"
export AIRFLOW__CORE__LOAD_EXAMPLES=False
mkdir -p "$AIRFLOW__CORE__DAGS_FOLDER"
uv run --frozen --no-dev --project providers/standard --with "pandas>=3,<4"
airflow db migrate
uv venv --allow-existing "$DEMO_DIR/pandas2-venv"
uv pip install --python "$DEMO_DIR/pandas2-venv/bin/python" "pandas>=2.2,<3"
```
2. Create Dags
```sh
cat > "$AIRFLOW__CORE__DAGS_FOLDER/virtualenv_example.py" <<'EOF'
from __future__ import annotations
from airflow.sdk import dag, task
@dag(schedule=None)
def virtualenv_example():
@task.virtualenv(requirements=["pandas>=2.2,<3"],
system_site_packages=False)
def virtualenv_sync():
import sys
import pandas as pd
return {"python": sys.executable, "pandas": pd.__version__}
@task.virtualenv(requirements=["pandas>=2.2,<3"],
system_site_packages=False)
async def virtualenv_async():
import sys
import pandas as pd
return {"python": sys.executable, "pandas": pd.__version__}
virtualenv_sync()
virtualenv_async()
example_dag = virtualenv_example()
if __name__ == "__main__":
dag_run = example_dag.test()
for ti in sorted(dag_run.get_task_instances(), key=lambda ti:
ti.task_id):
print(f"EXAMPLE STATE {ti.task_id} {ti.state}")
EOF
cat > "$AIRFLOW__CORE__DAGS_FOLDER/external_python_example.py" <<'EOF'
from __future__ import annotations
import os
from airflow.sdk import dag, task
PANDAS2_PYTHON = os.path.join(os.environ["DEMO_DIR"], "pandas2-venv", "bin",
"python")
@dag(schedule=None)
def external_python_example():
@task.external_python(python=PANDAS2_PYTHON)
def external_python_sync():
import sys
import pandas as pd
return {"python": sys.executable, "pandas": pd.__version__}
@task.external_python(python=PANDAS2_PYTHON)
async def external_python_async():
import sys
import pandas as pd
return {"python": sys.executable, "pandas": pd.__version__}
external_python_sync()
external_python_async()
example_dag = external_python_example()
if __name__ == "__main__":
dag_run = example_dag.test()
for ti in sorted(dag_run.get_task_instances(), key=lambda ti:
ti.task_id):
print(f"EXAMPLE STATE {ti.task_id} {ti.state}")
EOF
```
3. Run dags to check result
On main branch:
```sh
uv run --frozen --no-dev --project providers/standard --with "pandas>=3,<4"
python "$AIRFLOW__CORE__DAGS_FOLDER/virtualenv_example.py" 2>&1 | grep -oE
"running task <TaskInstance: [^ ]+|Returned value was: \{[^}]*\}|EXAMPLE STATE
.*"
uv run --frozen --no-dev --project providers/standard --with "pandas>=3,<4"
python "$AIRFLOW__CORE__DAGS_FOLDER/external_python_example.py" 2>&1 | grep -oE
"running task <TaskInstance: [^ ]+|Returned value was: \{[^}]*\}|EXAMPLE STATE
.*"
```
All four tasks succeed. The sync tasks report pandas 2 from the virtualenv
or the external environment. The async tasks report pandas 3 from the `uv run`
environment, which shows they ignored `requirements` and `python`:
```text
running task <TaskInstance: virtualenv_example.virtualenv_sync
Returned value was: {'python': '.../venvymv4y504/bin/python', 'pandas':
'2.3.3'}
running task <TaskInstance: virtualenv_example.virtualenv_async
Returned value was: {'python': '.../builds-v0/.tmpDC6uaL/bin/python',
'pandas': '3.0.5'}
EXAMPLE STATE virtualenv_sync success
EXAMPLE STATE virtualenv_async success
running task <TaskInstance: external_python_example.external_python_sync
Returned value was: {'python':
'.../airflow-async-venv-demo/pandas2-venv/bin/python', 'pandas': '2.3.3'}
running task <TaskInstance: external_python_example.external_python_async
Returned value was: {'python': '.../builds-v0/.tmp90SjJP/bin/python',
'pandas': '3.0.5'}
EXAMPLE STATE external_python_sync success
EXAMPLE STATE external_python_async success
```
On this branch, both Dags now fail at parse time.
```sh
uv run --frozen --no-dev --project providers/standard --with "pandas>=3,<4"
python "$AIRFLOW__CORE__DAGS_FOLDER/virtualenv_example.py" 2>&1 | grep
ValueError
uv run --frozen --no-dev --project providers/standard --with "pandas>=3,<4"
python "$AIRFLOW__CORE__DAGS_FOLDER/external_python_example.py" 2>&1 | grep
ValueError
```
```text
raise ValueError(
ValueError: _PythonVirtualenvDecoratedOperator does not support async
functions as python_callable. Call asyncio.run() inside a regular function
instead.
raise ValueError(
ValueError: _PythonExternalDecoratedOperator does not support async
functions as python_callable. Call asyncio.run() inside a regular function
instead.
```
<!-- SPDX-License-Identifier: Apache-2.0
https://www.apache.org/licenses/LICENSE-2.0 -->
<!--
Thank you for contributing!
Please provide above a brief description of the changes made in this pull
request.
Write a good git commit message following this guide:
https://chris.beams.io/posts/git-commit/
Please make sure that your code changes are covered with tests.
And in case of new features or big changes remember to adjust the
documentation.
For user-facing UI changes, please attach before/after screenshots (or a
short
screen recording) so reviewers can assess the visual impact.
Feel free to ping (in general) for the review if you do not see reaction for
a few days
(72 Hours is the minimum reaction time you can expect from volunteers) - we
sometimes miss notifications.
In case of an existing issue, reference it using one of the following:
* closes: #ISSUE
* related: #ISSUE
-->
---
##### Was generative AI tooling used to co-author this PR?
<!--
If generative AI tooling has been used in the process of authoring this PR,
please
change below checkbox to `[X]` followed by the name of the tool, uncomment
the "Generated-by".
-->
- [X] Yes - Claude Code
<!--
Generated-by: [Tool Name] following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
-->
---
* Read the **[Pull Request
Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)**
for more information. Note: commit author/co-author name and email in commits
become permanently public when merged.
* For fundamental code changes, an Airflow Improvement Proposal
([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals))
is needed.
* When adding dependency, check compliance with the [ASF 3rd Party License
Policy](https://www.apache.org/legal/resolved.html#category-x).
* For significant user-facing changes create newsfragment:
`{pr_number}.significant.rst`, in
[airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).
You can add this file in a follow-up commit after the PR is created so you
know the PR number.
--
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]