ashb commented on code in PR #57970:
URL: https://github.com/apache/airflow/pull/57970#discussion_r2504481708


##########
airflow-core/src/airflow/configuration.py:
##########
@@ -239,6 +241,22 @@ def __init__(
         self._suppress_future_warnings = False
         self._providers_configuration_loaded = False
 
+    @property
+    def _lookup_sequence(self) -> list[Callable]:
+        """
+        Define the sequence of lookup methods for get().
+
+        Subclasses can override this to sustomise lookup order.

Review Comment:
   ```suggestion
           Subclasses can override this to customise lookup order.
   ```



##########
airflow-core/tests/unit/core/test_configuration.py:
##########
@@ -1088,6 +1088,39 @@ def test_order_of_secrets_backends_and_kwargs_on_workers(
             for key, value in expected_backend_kwargs.items():
                 assert getattr(secrets_backend, key) == value
 
+    def test_lookup_sequence_order(self):
+        """Test that lookup sequence follows correct priority order."""
+        test_conf = AirflowConfigParser()
+        sequence = test_conf._lookup_sequence
+
+        assert sequence == [
+            test_conf._get_environment_variables,
+            test_conf._get_option_from_config_file,
+            test_conf._get_option_from_commands,
+            test_conf._get_option_from_secrets,
+            test_conf._get_option_from_defaults,
+            test_conf._get_option_from_provider_fallbacks,
+        ]
+
+    def test_lookup_sequence_override(self):
+        """Test that subclasses can override lookup sequence."""
+
+        class MyAirflowConfigPrser(AirflowConfigParser):
+            @property
+            def _lookup_sequence(self) -> list[callable]:
+                return [
+                    self._get_option_from_secrets,
+                    self._get_option_from_config_file,
+                ]
+
+        test_conf = MyAirflowConfigPrser()
+        myorder = test_conf._lookup_sequence
+        assert len(myorder) == 2
+        assert myorder == [
+            test_conf._get_option_from_secrets,
+            test_conf._get_option_from_config_file,
+        ]
+

Review Comment:
   Honestly: I would remove these tests. It is just writing the same thing 
twice, and I can't think of a case where these tests would help.
   
   You _could_ have one like the override where you check that an env var is 
not respected. But even that feels optional to me.



##########
airflow-core/src/airflow/configuration.py:
##########
@@ -972,131 +990,159 @@ def get_mandatory_list_value(self, section: str, key: 
str, **kwargs) -> list[str
             raise ValueError(f"The value {section}/{key} should be set!")
         return value
 
-    @overload  # type: ignore[override]
-    def get(self, section: str, key: str, fallback: str = ..., **kwargs) -> 
str: ...
+    def _get_option_from_defaults(
+        self,
+        deprecated_key: str | None,
+        deprecated_section: str | None,
+        key: str,
+        section: str,
+        issue_warning: bool = True,
+        extra_stacklevel: int = 0,
+        **kwargs,
+    ) -> str | object:
+        """Get config option from default values."""
+        if self.get_default_value(section, key) is not None or "fallback" in 
kwargs:
+            return expand_env_var(self.get_default_value(section, key, 
**kwargs))
+        return VALUE_NOT_FOUND_SENTINEL
 
-    @overload
-    def get(self, section: str, key: str, **kwargs) -> str | None: ...
+    def _get_option_from_provider_fallbacks(
+        self,
+        deprecated_key: str | None,
+        deprecated_section: str | None,
+        key: str,
+        section: str,
+        issue_warning: bool = True,
+        extra_stacklevel: int = 0,
+        **kwargs,
+    ) -> str | object:
+        """Get config option from provider fallback defaults."""
+        if self.get_provider_config_fallback_defaults(section, key) is not 
None:
+            # no expansion needed
+            return self.get_provider_config_fallback_defaults(section, key, 
**kwargs)
+        return VALUE_NOT_FOUND_SENTINEL
 
-    def get(  # type: ignore[misc]
+    def _resolve_deprecated_lookup(
         self,
         section: str,
         key: str,
-        suppress_warnings: bool = False,
-        lookup_from_deprecated: bool = True,
-        _extra_stacklevel: int = 0,
-        team_name: str | None = None,
-        **kwargs,
-    ) -> str | None:
+        lookup_from_deprecated: bool,
+        extra_stacklevel: int = 0,
+    ) -> tuple[str, str, str | None, str | None, bool]:
+        """
+        Resolve deprecated section/key mappings and determine deprecated 
values.
+
+        :param section: Section name (will be lowercased)
+        :param key: Key name (will be lowercased)
+        :param lookup_from_deprecated: Whether to lookup from deprecated 
options
+        :param extra_stacklevel: Extra stack level for warnings
+        :return: Tuple of (resolved_section, resolved_key, deprecated_section, 
deprecated_key, warning_emitted)
+        """
         section = section.lower()
         key = key.lower()
         warning_emitted = False
         deprecated_section: str | None = None
         deprecated_key: str | None = None
 
-        if lookup_from_deprecated:
-            option_description = (
-                self.configuration_description.get(section, {}).get("options", 
{}).get(key, {})
+        if not lookup_from_deprecated:
+            return section, key, deprecated_section, deprecated_key, 
warning_emitted
+
+        option_description = (
+            self.configuration_description.get(section, {}).get("options", 
{}).get(key, {})
+            if self.configuration_description
+            else {}
+        )
+        if option_description.get("deprecated"):
+            deprecation_reason = option_description.get("deprecation_reason", 
"")
+            warnings.warn(
+                f"The '{key}' option in section {section} is deprecated. 
{deprecation_reason}",
+                DeprecationWarning,
+                stacklevel=2 + extra_stacklevel,
             )
-            if option_description.get("deprecated"):
-                deprecation_reason = 
option_description.get("deprecation_reason", "")
+        # For the cases in which we rename whole sections
+        if section in self.inversed_deprecated_sections:
+            deprecated_section, deprecated_key = (section, key)
+            section = self.inversed_deprecated_sections[section]
+            if not self._suppress_future_warnings:
                 warnings.warn(
-                    f"The '{key}' option in section {section} is deprecated. 
{deprecation_reason}",
-                    DeprecationWarning,
-                    stacklevel=2 + _extra_stacklevel,
+                    f"The config section [{deprecated_section}] has been 
renamed to "
+                    f"[{section}]. Please update your `conf.get*` call to use 
the new name",
+                    FutureWarning,
+                    stacklevel=2 + extra_stacklevel,
                 )
-            # For the cases in which we rename whole sections
-            if section in self.inversed_deprecated_sections:
-                deprecated_section, deprecated_key = (section, key)
-                section = self.inversed_deprecated_sections[section]
-                if not self._suppress_future_warnings:
-                    warnings.warn(
-                        f"The config section [{deprecated_section}] has been 
renamed to "
-                        f"[{section}]. Please update your `conf.get*` call to 
use the new name",
-                        FutureWarning,
-                        stacklevel=2 + _extra_stacklevel,
-                    )
-                # Don't warn about individual rename if the whole section is 
renamed
-                warning_emitted = True
-            elif (section, key) in self.inversed_deprecated_options:
-                # Handle using deprecated section/key instead of the new 
section/key
-                new_section, new_key = 
self.inversed_deprecated_options[(section, key)]
-                if not self._suppress_future_warnings and not warning_emitted:
-                    warnings.warn(
-                        f"section/key [{section}/{key}] has been deprecated, 
you should use"
-                        f"[{new_section}/{new_key}] instead. Please update 
your `conf.get*` call to use the "
-                        "new name",
-                        FutureWarning,
-                        stacklevel=2 + _extra_stacklevel,
-                    )
-                    warning_emitted = True
-                deprecated_section, deprecated_key = section, key
-                section, key = (new_section, new_key)
-            elif section in self.deprecated_sections:
-                # When accessing the new section name, make sure we check 
under the old config name
-                deprecated_key = key
-                deprecated_section = self.deprecated_sections[section][0]
-            else:
-                deprecated_section, deprecated_key, _ = 
self.deprecated_options.get(
-                    (section, key), (None, None, None)
+            # Don't warn about individual rename if the whole section is 
renamed
+            warning_emitted = True
+        elif (section, key) in self.inversed_deprecated_options:
+            # Handle using deprecated section/key instead of the new 
section/key
+            new_section, new_key = self.inversed_deprecated_options[(section, 
key)]
+            if not self._suppress_future_warnings and not warning_emitted:
+                warnings.warn(
+                    f"section/key [{section}/{key}] has been deprecated, you 
should use"
+                    f"[{new_section}/{new_key}] instead. Please update your 
`conf.get*` call to use the "
+                    "new name",
+                    FutureWarning,
+                    stacklevel=2 + extra_stacklevel,
                 )
-        # first check environment variables
-        option = self._get_environment_variables(
-            deprecated_key,
-            deprecated_section,
-            key,
-            section,
-            issue_warning=not warning_emitted,
-            extra_stacklevel=_extra_stacklevel,
-            team_name=team_name,
-        )
-        if option is not None:
-            return option
+                warning_emitted = True
+            deprecated_section, deprecated_key = section, key
+            section, key = (new_section, new_key)
+        elif section in self.deprecated_sections:
+            # When accessing the new section name, make sure we check under 
the old config name
+            deprecated_key = key
+            deprecated_section = self.deprecated_sections[section][0]
+        else:
+            deprecated_section, deprecated_key, _ = 
self.deprecated_options.get(
+                (section, key), (None, None, None)
+            )
 
-        # ...then the config file
-        option = self._get_option_from_config_file(
-            deprecated_key,
-            deprecated_section,
-            key,
-            kwargs,
-            section,
-            issue_warning=not warning_emitted,
-            extra_stacklevel=_extra_stacklevel,
-        )
-        if option is not None:
-            return option
+        return section, key, deprecated_section, deprecated_key, 
warning_emitted
 
-        # ...then commands
-        option = self._get_option_from_commands(
-            deprecated_key,
-            deprecated_section,
-            key,
-            section,
-            issue_warning=not warning_emitted,
-            extra_stacklevel=_extra_stacklevel,
-        )
-        if option is not None:
-            return option
+    @overload  # type: ignore[override]
+    def get(self, section: str, key: str, fallback: str = ..., **kwargs) -> 
str: ...
+
+    @overload  # type: ignore[override]
+    def get(self, section: str, key: str, **kwargs) -> str | None: ...

Review Comment:
   Nit: this is wrong, -- if fallback isn't specified, this won't return None, 
it will raise an AirflowConfigurationException I think.
   
   (Lets fix that in a second PR)



##########
airflow-core/src/airflow/configuration.py:
##########
@@ -1132,7 +1179,8 @@ def _get_option_from_commands(
         section: str,
         issue_warning: bool = True,
         extra_stacklevel: int = 0,
-    ) -> str | None:
+        **kwargs,
+    ) -> str | object:

Review Comment:
   I don't love this being typed as `| object` -- perhaps we should do
   
   
   ```python
   class ValueNotFound: pass
   VALUE_NOT_FOUND_SENTINEL = ValueNotFound()
   ```
   
   Then this could be 
   
   ```suggestion
       ) -> str | ValueNotFound:
   ```
   
   Not a big thing, but might be worth it



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