Skip to content

Add support for async callables in PythonOperator - #60268

Merged
kaxil merged 21 commits into
apache:mainfrom
dabla:feature/async-python-operator
Jan 15, 2026
Merged

Add support for async callables in PythonOperator#60268
kaxil merged 21 commits into
apache:mainfrom
dabla:feature/async-python-operator

Conversation

@dabla

@dabladabla commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

This PR is related to the discussion I started on the devlist and which allows you to natively execute async code on PythonOperators.

There is also an AIP for this: https://cwiki.apache.org/confluence/display/AIRFLOW/%5BWIP%5D+AIP-98%3A+Rethinking+deferrable+operators%2C+async+hooks+and+performance+in+Airflow+3

Below an example which show you how it can be used with async hooks:

@task(show_return_value_in_logs=False)
async def load_xml_files(files):
import asyncio
from io import BytesIO
from more_itertools import chunked
from os import cpu_count
from tenacity import retry, stop_after_attempt, wait_fixed
from airflow.providers.sftp.pools.sftp import SFTPClientPool
print("number of files:", len(files))
async with SFTPClientPool(sftp_conn_id=sftp_conn, pool_size=cpu_count()) as pool:
@retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
async def download_file(file):
async with pool.get_sftp_client() as sftp:
print("downloading:", file)
buffer = BytesIO()
async with sftp.open(file, encoding=xml_encoding) as remote_file:
data = await remote_file.read()
buffer.write(data.encode(xml_encoding))
buffer.seek(0)
return buffer
for batch in chunked(files, cpu_count() * 2):
tasks = [asyncio.create_task(download_file(f)) for f in batch]
# Wait for this batch to finish before starting the next
for task in asyncio.as_completed(tasks):
result = await task
# Do something with result or accumulate it and return it as an XCom

This PR will fix additional remarks made by @kaxil on the original PR which has been reverted.


^ Add meaningful description above
Read the Pull Request Guidelines for more information.
In case of fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in a newsfragment file, named {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

@dabladabla changed the title Feature/async python operatorAdd support for async callables in PythonOperatorJan 8, 2026
Comment threadairflow-core/src/airflow/jobs/triggerer_job_runner.py
@dabla
dabla requested a review from uranusjrJanuary 9, 2026 08:41
@dabla
dablaforce-pushed the feature/async-python-operator branch from 9124c9a to cd4ec26CompareJanuary 9, 2026 12:58
@dabladabla closed this Jan 9, 2026
@dabla
dablaforce-pushed the feature/async-python-operator branch from ebf2873 to 0b341e6CompareJanuary 9, 2026 13:14
…epends on modified comms supervisor which cannot be backported to older Airflow versions
Add support for async callables in PythonOperator (apache#59087)
* refactor: Implemented BaseAsyncOperator in task-sdk
* refactor: Now PythonOperator extends BaseAsyncOperator
* refactor: Also implement BaseAsyncOperator in common-compat provider to support older Airflow versions
---------
Co-authored-by: Jason(Zhe-You) Liu <68415893+jason810496@users.noreply.github.com>
(cherry picked from commit 9cab6fb)
@dabladabla reopened this Jan 9, 2026
@dabla

Copy link
Copy Markdown
ContributorAuthor

Almost there, once newsfragment is added we are good

And the newfragment ;-)

@dabla

Copy link
Copy Markdown
ContributorAuthor

@kaxil If newsfragment is ok for you then I think it's finally ready to merge.

@kaxil

Copy link
Copy Markdown
Member

Static check is failing, worth looking at https://github.com/apache/airflow/blob/main/airflow-core/newsfragments/54505.significant.rst or other examples

@dabla

Copy link
Copy Markdown
ContributorAuthor

Since rebase of PR branch I now get following error, which is strange as the 'use next version' is defined in toml file:

common.compat changed with providers: ['standard']
common.compat provider changed but the following providers don't have '# use next version' comment for their common-compat dependency!
- standard (providers/standard/pyproject.toml)
When common.compat changes with other providers in the same PR, add '# use next version' comment where they depend on common-compat.
Example: "apache-airflow-providers-common-compat>=1.8.0", # use next version
To bypass this check, add the label: 'skip common compat check'
Error: Process completed with exit code 1.

@dabla
dabla requested a review from kaxilJanuary 14, 2026 08:29
@kaxil

Copy link
Copy Markdown
Member

@dabla I think that was because the versions were bumped for the release yesterday: #60437

So you'd need to update the following line:

"apache-airflow-providers-common-compat",

to add # use next version

@dabla

Copy link
Copy Markdown
ContributorAuthor

@dabla I think that was because the versions were bumped for the release yesterday: #60437

So you'd need to update the following line:

"apache-airflow-providers-common-compat",

to add # use next version

Yes indeed, cause I knew I added it, but due to rebase it was gone again as versions where indeed bumped. Re-added it now so hopefully we will be fine.

@dabla

Copy link
Copy Markdown
ContributorAuthor

@kaxil last build was fine, I suppose we can merge now?

@kaxil
kaxil merged commit faf847c into apache:mainJan 15, 2026
129 checks passed
jason810496 pushed a commit to jason810496/airflow that referenced this pull request Jan 22, 2026
This PR is related to the discussion I started on the [devlist](https://lists.apache.org/thread/ztnfsqolow4v1zsv4pkpnxc1fk0hbf2p) and which allows you to natively execute async code on PythonOperators.
There is also an AIP for this: https://cwiki.apache.org/confluence/display/AIRFLOW/%5BWIP%5D+AIP-98%3A+Rethinking+deferrable+operators%2C+async+hooks+and+performance+in+Airflow+3
Below an example which show you how it can be used with async hooks:
```
@task(show_return_value_in_logs=False)
async def load_xml_files(files):
import asyncio
from io import BytesIO
from more_itertools import chunked
from os import cpu_count
from tenacity import retry, stop_after_attempt, wait_fixed
from airflow.providers.sftp.hooks.sftp import SFTPClientPool
print("number of files:", len(files))
async with SFTPClientPool(sftp_conn_id=sftp_conn, pool_size=cpu_count()) as pool:
@Retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
async def download_file(file):
async with pool.get_sftp_client() as sftp:
print("downloading:", file)
buffer = BytesIO()
async with sftp.open(file, encoding=xml_encoding) as remote_file:
data = await remote_file.read()
buffer.write(data.encode(xml_encoding))
buffer.seek(0)
return buffer
for batch in chunked(files, cpu_count() * 2):
tasks = [asyncio.create_task(download_file(f)) for f in batch]
# Wait for this batch to finish before starting the next
for task in asyncio.as_completed(tasks):
result = await task
# Do something with result or accumulate it and return it as an XCom
```
This PR will fix additional remarks made by @kaxil on the original [PR](apache#59087) which has been reverted.
jhgoebbert pushed a commit to jhgoebbert/airflow_Owen-CH-Leung that referenced this pull request Feb 8, 2026
This PR is related to the discussion I started on the [devlist](https://lists.apache.org/thread/ztnfsqolow4v1zsv4pkpnxc1fk0hbf2p) and which allows you to natively execute async code on PythonOperators.
There is also an AIP for this: https://cwiki.apache.org/confluence/display/AIRFLOW/%5BWIP%5D+AIP-98%3A+Rethinking+deferrable+operators%2C+async+hooks+and+performance+in+Airflow+3
Below an example which show you how it can be used with async hooks:
```
@task(show_return_value_in_logs=False)
async def load_xml_files(files):
import asyncio
from io import BytesIO
from more_itertools import chunked
from os import cpu_count
from tenacity import retry, stop_after_attempt, wait_fixed
from airflow.providers.sftp.hooks.sftp import SFTPClientPool
print("number of files:", len(files))
async with SFTPClientPool(sftp_conn_id=sftp_conn, pool_size=cpu_count()) as pool:
@Retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
async def download_file(file):
async with pool.get_sftp_client() as sftp:
print("downloading:", file)
buffer = BytesIO()
async with sftp.open(file, encoding=xml_encoding) as remote_file:
data = await remote_file.read()
buffer.write(data.encode(xml_encoding))
buffer.seek(0)
return buffer
for batch in chunked(files, cpu_count() * 2):
tasks = [asyncio.create_task(download_file(f)) for f in batch]
# Wait for this batch to finish before starting the next
for task in asyncio.as_completed(tasks):
result = await task
# Do something with result or accumulate it and return it as an XCom
```
This PR will fix additional remarks made by @kaxil on the original [PR](apache#59087) which has been reverted.
choo121600 pushed a commit to choo121600/airflow that referenced this pull request Feb 22, 2026
This PR is related to the discussion I started on the [devlist](https://lists.apache.org/thread/ztnfsqolow4v1zsv4pkpnxc1fk0hbf2p) and which allows you to natively execute async code on PythonOperators.
There is also an AIP for this: https://cwiki.apache.org/confluence/display/AIRFLOW/%5BWIP%5D+AIP-98%3A+Rethinking+deferrable+operators%2C+async+hooks+and+performance+in+Airflow+3
Below an example which show you how it can be used with async hooks:
```
@task(show_return_value_in_logs=False)
async def load_xml_files(files):
import asyncio
from io import BytesIO
from more_itertools import chunked
from os import cpu_count
from tenacity import retry, stop_after_attempt, wait_fixed
from airflow.providers.sftp.hooks.sftp import SFTPClientPool
print("number of files:", len(files))
async with SFTPClientPool(sftp_conn_id=sftp_conn, pool_size=cpu_count()) as pool:
@Retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
async def download_file(file):
async with pool.get_sftp_client() as sftp:
print("downloading:", file)
buffer = BytesIO()
async with sftp.open(file, encoding=xml_encoding) as remote_file:
data = await remote_file.read()
buffer.write(data.encode(xml_encoding))
buffer.seek(0)
return buffer
for batch in chunked(files, cpu_count() * 2):
tasks = [asyncio.create_task(download_file(f)) for f in batch]
# Wait for this batch to finish before starting the next
for task in asyncio.as_completed(tasks):
result = await task
# Do something with result or accumulate it and return it as an XCom
```
This PR will fix additional remarks made by @kaxil on the original [PR](apache#59087) which has been reverted.
Subham-KRLX pushed a commit to Subham-KRLX/airflow that referenced this pull request Mar 4, 2026
This PR is related to the discussion I started on the [devlist](https://lists.apache.org/thread/ztnfsqolow4v1zsv4pkpnxc1fk0hbf2p) and which allows you to natively execute async code on PythonOperators.
There is also an AIP for this: https://cwiki.apache.org/confluence/display/AIRFLOW/%5BWIP%5D+AIP-98%3A+Rethinking+deferrable+operators%2C+async+hooks+and+performance+in+Airflow+3
Below an example which show you how it can be used with async hooks:
```
@task(show_return_value_in_logs=False)
async def load_xml_files(files):
import asyncio
from io import BytesIO
from more_itertools import chunked
from os import cpu_count
from tenacity import retry, stop_after_attempt, wait_fixed
from airflow.providers.sftp.hooks.sftp import SFTPClientPool
print("number of files:", len(files))
async with SFTPClientPool(sftp_conn_id=sftp_conn, pool_size=cpu_count()) as pool:
@Retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
async def download_file(file):
async with pool.get_sftp_client() as sftp:
print("downloading:", file)
buffer = BytesIO()
async with sftp.open(file, encoding=xml_encoding) as remote_file:
data = await remote_file.read()
buffer.write(data.encode(xml_encoding))
buffer.seek(0)
return buffer
for batch in chunked(files, cpu_count() * 2):
tasks = [asyncio.create_task(download_file(f)) for f in batch]
# Wait for this batch to finish before starting the next
for task in asyncio.as_completed(tasks):
result = await task
# Do something with result or accumulate it and return it as an XCom
```
This PR will fix additional remarks made by @kaxil on the original [PR](apache#59087) which has been reverted.
Ankurdeewan pushed a commit to Ankurdeewan/airflow that referenced this pull request Mar 15, 2026
This PR is related to the discussion I started on the [devlist](https://lists.apache.org/thread/ztnfsqolow4v1zsv4pkpnxc1fk0hbf2p) and which allows you to natively execute async code on PythonOperators.
There is also an AIP for this: https://cwiki.apache.org/confluence/display/AIRFLOW/%5BWIP%5D+AIP-98%3A+Rethinking+deferrable+operators%2C+async+hooks+and+performance+in+Airflow+3
Below an example which show you how it can be used with async hooks:
```
@task(show_return_value_in_logs=False)
async def load_xml_files(files):
import asyncio
from io import BytesIO
from more_itertools import chunked
from os import cpu_count
from tenacity import retry, stop_after_attempt, wait_fixed
from airflow.providers.sftp.hooks.sftp import SFTPClientPool
print("number of files:", len(files))
async with SFTPClientPool(sftp_conn_id=sftp_conn, pool_size=cpu_count()) as pool:
@Retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
async def download_file(file):
async with pool.get_sftp_client() as sftp:
print("downloading:", file)
buffer = BytesIO()
async with sftp.open(file, encoding=xml_encoding) as remote_file:
data = await remote_file.read()
buffer.write(data.encode(xml_encoding))
buffer.seek(0)
return buffer
for batch in chunked(files, cpu_count() * 2):
tasks = [asyncio.create_task(download_file(f)) for f in batch]
# Wait for this batch to finish before starting the next
for task in asyncio.as_completed(tasks):
result = await task
# Do something with result or accumulate it and return it as an XCom
```
This PR will fix additional remarks made by @kaxil on the original [PR](apache#59087) which has been reverted.
@vatsrahul1001vatsrahul1001 added this to the Airflow 3.2.0 milestone Apr 7, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@dabla@kaxil@potiuk@uranusjr@kacpermuda@vatsrahul1001@davidblain-infrabel