shahar1 commented on code in PR #73209:
URL: https://github.com/apache/airflow/pull/73209#discussion_r4245066482


##########
airflow-core/src/airflow/dag_processing/bundles/provider.py:
##########
@@ -0,0 +1,139 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review Comment:
   1. I'm quite concerned of overloading the term "Provider", which has a very 
clear meaning in Airflow, and using it in this context might create some 
confusion. 
   
   Suggested alternative: "DagBundleSource"? (Like we have AirflowPluginSource)
   
   2. Please add a short "writing a bundle source" (or w/e we choose to call 
it) section to dag-bundles.rst



##########
airflow-core/src/airflow/dag_processing/bundles/manager.py:
##########


Review Comment:
   `DagBundlesManager()` now imports bundle classes lazily via 
`ConfigDagBundleProvider`, so an invalid classpath raises here instead of 
failing the Dag processor at startup, as it did on main.
   
   After `continue`, the bundle remains in `stored`, but cleanup marks it 
inactive, removes its team and import errors, and leaves its Dags stale. With 
`--bundle-name`, users see "Bundles not found" instead of the actual import 
error.
   
   Could we skip only the URL update on failure while keeping the bundle 
active? A regression test with an existing bundle and an invalid classpath 
would help.



##########
airflow-core/src/airflow/dag_processing/bundles/manager.py:
##########
@@ -309,39 +177,76 @@ class DagBundlesManager(LoggingMixin):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self._bundle_config: dict[str, _InternalBundleConfig] = {}
-        self.parse_config()
+        self._bundle_provider = self._load_bundle_provider()
 
-    def parse_config(self) -> None:
-        """
-        Get all DAG bundle configurations and store in instance variable.
+    @cached_property
+    def _example_dag_bundle_paths(self) -> dict[str, str]:
+        if not conf.getboolean("core", "LOAD_EXAMPLES"):
+            return {}
+        return _discover_example_dag_bundle_paths()
+
+    @staticmethod
+    def _load_bundle_provider() -> DagBundleProvider:
+        provider_class = conf.getimport("dag_processor", "dag_bundle_provider")
+        if not isinstance(provider_class, type) or not 
issubclass(provider_class, DagBundleProvider):
+            raise AirflowConfigException(
+                "The class configured in section `dag_processor` key 
`dag_bundle_provider` "
+                "must extend DagBundleProvider."
+            )
+        try:
+            return provider_class()
+        except AirflowConfigException:
+            raise
+        except Exception as e:
+            raise AirflowConfigException(
+                "Could not initialize the Dag bundle provider configured in 
section "
+                "`dag_processor` key `dag_bundle_provider`."
+            ) from e
+
+    def _get_provider_bundle_metadata(self) -> tuple[DagBundleMetadata, ...]:
+        """Get validated metadata returned by the configured Dag bundle 
provider."""
+        bundle_metadata: dict[str, DagBundleMetadata] = {}
+        for metadata in self._bundle_provider.get_configured_bundle_metadata():
+            if not isinstance(metadata, DagBundleMetadata):
+                raise AirflowConfigException(
+                    "Dag bundle providers must return DagBundleMetadata 
objects from "
+                    "get_configured_bundle_metadata()."
+                )
+            if metadata.name == _example_dag_bundle_name:
+                raise AirflowConfigException(
+                    f"Bundle name '{_example_dag_bundle_name}' is a reserved 
name. Please choose another name for your bundle."
+                    " Example Dags can be enabled with the '[core] 
load_examples' config."
+                )
+            if metadata.name in bundle_metadata:
+                raise AirflowConfigException(
+                    f"Dag bundle provider returned duplicate bundle name 
'{metadata.name}'."
+                )
+            if metadata.team_name and not conf.getboolean("core", 
"multi_team"):
+                raise AirflowConfigException(
+                    "Dag bundle metadata cannot have a team name when 
multi-team mode is disabled. "
+                    "To enable multi-team, update section `core` key 
`multi_team` in your config."
+                )
+            bundle_metadata[metadata.name] = metadata
 
-        If a bundle class for a given name has already been imported, it will 
not be imported again.
+        return tuple(bundle_metadata.values())
 
-        :meta private:
-        """
-        if self._bundle_config:
-            return
+    def get_active_bundle_metadata(self) -> tuple[DagBundleMetadata, ...]:
+        """Get metadata for all active Dag bundles, including example 
bundles."""
+        bundle_metadata = {metadata.name: metadata for metadata in 
self._get_provider_bundle_metadata()}
+        if not bundle_metadata:
+            return ()
 
-        bundle_config_list = _load_bundle_config_snapshot().configs
-        if not bundle_config_list:
-            return
+        # Airflow owns example Dag bundles; configured providers do not need 
to include them.

Review Comment:
   Could you please drop this comment and make sure that the new behavior is 
covered with tests?



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