Skip to content

Move provider examples to their respective folders#57320

Closed
bhavaniravi wants to merge 2 commits into
apache:mainfrom
bhavaniravi:bhavani/load-eg-from-providers
Closed

Move provider examples to their respective folders#57320
bhavaniravi wants to merge 2 commits into
apache:mainfrom
bhavaniravi:bhavani/load-eg-from-providers

Conversation

@bhavaniravi

@bhavaniravi bhavaniravi commented Oct 27, 2025

Copy link
Copy Markdown
Contributor

Fixes : #52469

  1. Loading example DAGs purely as bundles
  2. Removing the link(copy) between standard example_dags and airflow-core/standard/example_dags
  3. Fixing testcases to use bundles instead of dagbag folders
  4. fixing testcases to use example_dags from the standard module

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

1. loading example dags as bundles
2. fixing testcases to use bundles instead of dagbag folders
3. fixing testcases to use example_dags from standard module

if include_examples:
from airflow import example_dags
# if include_examples:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change felt bold. Which is why I commented it out. If we were to let DagBag load examples, then we are duplicating the provider examples loading in both Dagbundle and in dagbag.

Will this affect backward compatibility?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it sfeels hard - I assume this is the core thing to remove.

I assume this is a victim of the surgery but later all Dags will be loaded from standard provider directly.

If there are legacy side effects we need to clean them up. So you are doing the "right" thing.
(And please remove commented-out code :-D before merge)


with create_session() as session:
if AIRFLOW_V_3_0_PLUS:
DagBundlesManager().sync_bundles_to_db(session=session)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something that threw me off, DagBundlesManager().sync_bundles_to_db(session=session), I assumed it would create the Dag objects in the DB, but it didn't, hence the snippet below

Was it a conscious choice to keep the DAG parsing and DB entries creation to dagbag alone?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that is really a bit strange. Unfortunately @jedcunningham is still on leave, @dstandish do you know why the signature of the method is mis-leading?

@bhavaniravi bhavaniravi force-pushed the bhavani/load-eg-from-providers branch 2 times, most recently from dc82c10 to 1f893a0 Compare October 27, 2025 08:49
# Rebuild Test DB for other tests
clear_db_dags()
parse_and_sync_to_db(os.devnull, include_examples=True)
self.setup_class()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR changes the example_dags loaded in Airflow, especially testcases. That leads us to add include_examples parameter, like we do for DagBag

We have two options

  1. Introducing the include_examples parameter to DagBundleManager just to satisfy testing needs
  2. setting conf_vars({("core", "load_examples"): "True"}): at testcase level wherever needed

Choosing (2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also propose to choose 2.

In my mind making code in a structure for testability is great but adding balconies for testing in productive code has the danger of future "mis-use"/"unintended use".

Side effect is that we need this very much often, so in regards of convenience maybe a dedicated decorator might be nice (but not critical, just nice) and in regards of performance might be needed to cache examples to prevent heavy and often re-parse as this will maybe impact performance of testing if re-parsed often.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading more context of the change I assume this is a bit of a mis-conception how the previous code was constructured. Calling the "setup_class()" or a re-sync of dags is conceptually wrong. Not for your change but in general. If the test "messes up" the dags and side effects should be prevented then a proper fixture is needed and it should not be made in a way that the initialization is called again... hard to get the context and error prone as missing might make other test cases broken...

Not sure, might not be fixed here but would be a good issue in another PR to improve.

@bhavaniravi bhavaniravi force-pushed the bhavani/load-eg-from-providers branch from 1f893a0 to 9a7e97d Compare October 27, 2025 13:02
@bhavaniravi bhavaniravi requested a review from vincbeck as a code owner October 27, 2025 13:02
@bhavaniravi bhavaniravi force-pushed the bhavani/load-eg-from-providers branch from 9a7e97d to 3f968dd Compare October 27, 2025 13:02
@bhavaniravi bhavaniravi force-pushed the bhavani/load-eg-from-providers branch from 3f968dd to 0b1808c Compare October 27, 2025 14:54
def _add_provider_example_dags_to_bundle(bundle_config_list: list[_ExternalBundleConfig]):
from airflow import providers

for provider_path in providers.__path__:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THat is a good approach but I assume we need to make a lookup to all providers via plugins_manager. There could also be (in theory) providers not coming from Apache Airflow and they might reside in other hierarchy outside of "airflow" package

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also some providers are nested one level down, like "common/sql" - so most probably via providers_manager we can discover the path/module name where stored... and if not we might need to extend provider.yaml to explicitly mark if examples are available.


def test_task_states_for_dag_run(self):
dag2 = DagBag().dags["example_python_operator"]
from airflow.providers.standard.example_dags.example_python_operator import dag as dag2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks OK but is a hard wiring from core to standard provider. Have no better idea but smells a bit "not good".

Might we need a fixture on a general level allowing dags to load w/o tainting individual tests with hard-wired dependencies?

from airflow.models.dag import DagModel
from airflow.models.dagwarning import DagWarning, DagWarningType
from airflow.models.serialized_dag import SerializedDagModel
from airflow.providers.standard import example_dags as standard_example_dags

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar as above but a bit pmore problematic - if we make this a global import the python module loader needs to have the dependency available at moment of collecting tests already. Can you make this a local import of a small utility function in the test or better to be a fixture that loads it at test runtime to prevent a file level import? (Alongside with code in line 56 and then use it in line 216 and 482 etc)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File with just a license header - committed by mistake of content missing?

@jscheffl

jscheffl commented Nov 2, 2025

Copy link
Copy Markdown
Contributor

Thanks for the PR! I think is is in a very good direction! Of course CI tests are not 100% ok but due to review I assume there are changes coming.

Looking forward for other reviews and changing example loading to the new era!

*) There might be some challenges because for example standard provider is used in other older airflow versions <3 but seems from CI state this is already mastered.

@potiuk

potiuk commented Nov 3, 2025

Copy link
Copy Markdown
Member

Indeed good start - I will chime in for the second iteration after applying the suggestions and commments from @jscheffl

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions.

@github-actions github-actions Bot added the stale Stale PRs per the .github/workflows/stale.yml policy file label Dec 19, 2025
@github-actions github-actions Bot closed this Jan 10, 2026
andreahlert added a commit to andreahlert/airflow that referenced this pull request Jun 8, 2026
Apply remaining suggestions from the original review of the closed
PR apache#57320:

- bundles/manager.py: document that sync_bundles_to_db only reconciles
  bundle metadata and does not parse or write DAG rows, clarifying the
  signature confusion raised by @bhavaniravi during review.
- tests/unit/dag_processing/test_dagbag.py: stop importing the standard
  provider's example_dags module at collection time. Move the import
  into a small helper plus a pytest fixture so tests that need the
  folder request it explicitly and the module remains collectable when
  the standard provider is not yet importable. The single parametrize
  case that referenced the folder now passes a relative file name and
  resolves the absolute path inside the test.
- tests/unit/cli/commands/test_task_command.py: build a minimal DAG
  inline in test_task_states_for_dag_run instead of importing one from
  the standard provider's example_dags. The test only checks CLI
  behaviour around a known dag_id/task_id, so reproducing the name and
  a single task is enough to keep the core test decoupled from the
  standard provider's example DAGs.

Signed-off-by: André Ahlert <[email protected]>
potiuk pushed a commit that referenced this pull request Jun 9, 2026
…#57320) (#66161)

* chore: move standard examples to provider
1. loading example dags as bundles
2. fixing testcases to use bundles instead of dagbag folders
3. fixing testcases to use example_dags from standard module

Signed-off-by: André Ahlert <[email protected]>

* fix: failing testcases to load examples form dagbundle

Signed-off-by: André Ahlert <[email protected]>

* Resolve provider example DAGs via ProvidersManager

Replace the directory walk over airflow.providers.__path__ with a
lookup based on ProvidersManager. The previous approach silently
skipped:

- nested providers like apache-airflow-providers-common-sql, whose
  module path is airflow.providers.common.sql (one level deeper);
- providers installed outside the airflow.providers namespace
  package, since they are not visible via os.listdir.

The new implementation iterates over the providers registered through
the apache_airflow_provider entry point, imports each provider module
and adds its example_dags folder when present. Bundle names are now
keyed on the canonical package name to keep them unique and stable
across deployments.

Signed-off-by: André Ahlert <[email protected]>

* Tests: relax test_get_all_bundle_names assertion

The set of provider example DAG bundles depends on which providers
expose an example_dags folder, which is environment specific. Pin
only the built-in bundles and assert the prefix of any extra entry.

Signed-off-by: André Ahlert <[email protected]>

* Address review feedback on example DAG loading

Apply remaining suggestions from the original review of the closed
PR #57320:

- bundles/manager.py: document that sync_bundles_to_db only reconciles
  bundle metadata and does not parse or write DAG rows, clarifying the
  signature confusion raised by @bhavaniravi during review.
- tests/unit/dag_processing/test_dagbag.py: stop importing the standard
  provider's example_dags module at collection time. Move the import
  into a small helper plus a pytest fixture so tests that need the
  folder request it explicitly and the module remains collectable when
  the standard provider is not yet importable. The single parametrize
  case that referenced the folder now passes a relative file name and
  resolves the absolute path inside the test.
- tests/unit/cli/commands/test_task_command.py: build a minimal DAG
  inline in test_task_states_for_dag_run instead of importing one from
  the standard provider's example_dags. The test only checks CLI
  behaviour around a known dag_id/task_id, so reproducing the name and
  a single task is enough to keep the core test decoupled from the
  standard provider's example DAGs.

Signed-off-by: André Ahlert <[email protected]>

* Tests: use SQLAlchemy 2.0 delete() in trigger_dagrun teardown

The teardown deletes a single DagBundleModel row keyed by name. Use
session.execute(delete(...).where(...)) instead of the deprecated
session.query(...).filter_by(...).delete() form so prek's
prevent-deprecated-sqlalchemy-usage hook stays clean.

Signed-off-by: André Ahlert <[email protected]>

* Tests: update bundle_name for example_python_operator

After moving the standard provider's example DAGs into their own
bundle, example_python_operator no longer lives in the dags-folder
bundle. Update the 24 expected payloads in test_task_instances.py
that asserted bundle_name='dags-folder' to the new
airflow-provider-apache-airflow-providers-standard-example-dags
bundle name. The lone sync_bag_to_db('dags-folder', ...) call in this
file is unrelated; it registers a synthetic dag built by dag_maker.

Signed-off-by: André Ahlert <[email protected]>

* Tests: avoid double-loading example DAGs in parse_and_sync_to_db

On Airflow 3.1+, parse_and_sync_to_db iterates every registered DAG
bundle (including example_dags and airflow-provider-*-example-dags)
and syncs them with their own bundle name. The leading
DagBag(dag_folder=folder, include_examples=True) was also pulling
example DAGs into the dags-folder bundle, so each example DAG ended
up registered under two different bundles. The duplicated rows then
violated the (asset_id, dag_id) unique constraint on
dag_schedule_asset_reference and broke unrelated tests (notably the
fab provider tests under compat).

Force include_examples=False on the 3.1+ path; the bundle loop is
already responsible for loading example DAGs from their own bundles.
The 2.x and 3.0 paths are untouched.

Signed-off-by: André Ahlert <[email protected]>

* Trigger CI rerun

Signed-off-by: André Ahlert <[email protected]>

* Update devel-common/src/tests_common/test_utils/db.py

Co-authored-by: Jens Scheffler <[email protected]>
Signed-off-by: André Ahlert <[email protected]>

* Update airflow-core/src/airflow/dag_processing/bundles/manager.py

Co-authored-by: Jens Scheffler <[email protected]>
Signed-off-by: André Ahlert <[email protected]>

* Address review feedback on provider example DAG bundle discovery

- Hoist `importlib`, `logging`, and `ProvidersManager` to module top in
  `dag_processing/bundles/manager.py` and add a module-level logger.
- Reverse the `apache-airflow-providers-` prefix check so the canonical
  case reads first, per @jscheffl's nit.
- Broaden the exception handler around `import_module` and the
  subsequent `__path__` access to `Exception` with `log.exception`, so a
  provider with a custom `__getattr__` can no longer crash config
  loading on the scheduler/api-server.
- Switch the provider-example bundle dedup key from `bundle_name` to
  the resolved `example_dag_folder` and document the namespace-package
  scenario the guard exists for (multiple `airflow.providers.common.*`
  distributions sharing one namespace via `pkgutil.extend_path`).
- Rename the per-provider bundle from
  `airflow-provider-{package}-example-dags` to
  `{package}-example-dags`, matching the package distribution name
  surfaced to users in REST API responses and `pip list`.
- Emit a `DeprecationWarning` from `DagBag.collect_dags` and from
  `tests_common.test_utils.db.parse_and_sync_to_db` when callers pass
  `include_examples=True`, and update the docstrings to direct callers
  at the `[core] load_examples` configuration option.
- Migrate the in-tree `parse_and_sync_to_db(..., include_examples=True)`
  callers to use `conf_vars({("core", "load_examples"): "true"})`.
- Update `test_task_instances.py` bundle-name assertions and the
  `test_get_all_bundle_names` suffix check for the new format.
- Add `airflow-core/newsfragments/66161.significant.rst` covering the
  user-visible REST-API bundle-name change.

The package-name -> module-path heuristic is kept for now; replacing it
with an authoritative field on `ProviderInfo` is tracked in #66305.

Signed-off-by: André Ahlert <[email protected]>

* Remove include_examples parameter from DagBag and test helpers

Drops the include_examples knob from DagBag.__init__, BundleDagBag.__init__,
collect_dags, and parse_and_sync_to_db. Example DAGs now come in exclusively
through the per-provider example bundles registered when [core] load_examples
is enabled. Removes the deprecation warnings, updates all callers across core,
devel-common, and providers, and gates the few provider tests that still need
to read examples on older Airflow versions behind AIRFLOW_V_3_3_PLUS.

Signed-off-by: André Ahlert <[email protected]>

* Fix mypy and test assertion after DagBag include_examples removal

The previous commit removed include_examples from DagBag callers but left
two follow-ups that broke prek mypy and the config CLI test:

- devel-common test_utils/db.py: pre-3.1 compat branches still call
  DagBag(include_examples=False) for older Airflow runtimes; mypy now
  flags it because the current source no longer accepts that kwarg. Add
  call-arg type-ignore (matches the existing attr-defined pattern on the
  sync_to_db calls below).
- test_config_command.py: assertions for conf.write(...) lost the
  include_examples=False kwarg, but conf.write is AirflowConfigParser.write
  (config-file examples), unrelated to the DagBag flag, and still passes
  it. Restore the kwarg in the expected call.

Signed-off-by: André Ahlert <[email protected]>

* Drop include_examples from new DagBag callers and gate dag_maker for compat

The merge with main brought in tests in airflow-core/tests/unit/models/test_dag.py
that still passed include_examples=False to DagBag, and the dag_maker fixture in
pytest_plugin.py was loading examples by default on Airflow <3.3 because the
removed kwarg flipped the effective default to True. That broke compat 3.0.6
runs because the cleanup path then tried to roll back a session that was never
attached. Drop the kwarg from the new test_dag.py callers and gate both
dag_maker and get_test_dag DagBag construction on AIRFLOW_V_3_3_PLUS so older
Airflow versions still pass include_examples=False explicitly.

Signed-off-by: André Ahlert <[email protected]>

* Gate DagBag include_examples on Airflow 3.3+ for older compat runs

The PR drops include_examples=False from a number of DagBag callers because
DagBag in 3.3+ no longer accepts the kwarg and never loads examples by default.
On compat runs against Airflow 3.1.x and 3.2.x, parse_and_sync_to_db hit a
branch that called DagBag(folder) with the old default include_examples=True,
which let the dags-folder DagBag pull in core example DAGs that the bundle
loop also synced, producing duplicate dag_schedule_asset_reference rows and a
UNIQUE constraint failure. On Airflow 2.11.1, DagBag callers in the standard
external_task_sensor and time_delta tests defaulted to include_examples=True
and pulled in example DAGs whose required Params lack defaults, breaking
ExternalTaskMarker tests with ParamValidationError.

Restrict the new bundle-aware sync path to AIRFLOW_V_3_3_PLUS and pass
include_examples=False explicitly on older Airflow versions in the standard
provider tests so compat 2.11.1, 3.1.8, and 3.2.1 keep their original
behaviour.

Signed-off-by: André Ahlert <[email protected]>

* Trim 66161 newsfragment to user-visible facts

DagBag is not part of the Airflow public interface, so removing
include_examples from DagBag/collect_dags/BundleDagBag/parse_and_sync_to_db
does not need a release-notes migration recipe. Drop the internal removal
block and the Types of change / Migration rules scaffolding, keep the
bundle naming change and the bundle_name REST API behaviour change.

Signed-off-by: André Ahlert <[email protected]>

---------

Signed-off-by: André Ahlert <[email protected]>
Co-authored-by: Bhavani Ravi <[email protected]>
Co-authored-by: Jens Scheffler <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Load example Dags from standard provider

3 participants