FrankYang0529 opened a new pull request, #72858:
URL: https://github.com/apache/airflow/pull/72858

   ## Why
   
   - An executor or auth manager configured by module path, without being 
packaged as a provider, loses every CLI command it defines in 
`get_cli_commands()`. The commands are missing with no warning and no error.
   - Before the provider `cli` section was introduced (#59805), `cli_parser` 
looped over every configured executor and called the configured auth manager.
   - `core-concepts/executor/index.rst` and 
`core-concepts/auth-manager/index.rst` still document `get_cli_commands()`, and 
the auth manager page still shows `[core] auth_manager = 
my_company.auth_managers.MyCustomAuthManager` as the way to configure a custom 
class.
   
   
   ## How
   
   - Build the set of class paths a provider `cli` section already covers, skip 
those, and send everything else through the fallback.
   - Skip classes shipped in airflow-core by module prefix, so the default 
configuration never imports them.
   - Give commands registered by a provider priority over the fallback. A 
subclass of a provider executor or auth manager inherits `get_cli_commands()`. 
Without this, it registers the same names a second time. `cli_parser` raises 
`CliConflictError` on a duplicate name and the CLI fails to start.
   
   ## Verification
   
   - Unit test: `uv run --project airflow-core pytest 
airflow-core/tests/unit/cli/test_cli_parser.py -q`
   - Integration test:
   
   1. Setup
   
   ```sh
   mkdir -p files/demo/my_company
   touch files/demo/my_company/__init__.py
   
   cat > files/demo/my_company/executors.py <<'EOF'
   from airflow.cli.cli_config import ActionCommand, GroupCommand
   from airflow.executors.base_executor import BaseExecutor
   from airflow.providers.celery.executors.celery_executor import CeleryExecutor
   
   
   def ping(args):
       print("pong from MyExecutor")
   
   
   class MyExecutor(BaseExecutor):
       """Plain custom executor: implements BaseExecutor, not packaged as a 
provider."""
   
       @staticmethod
       def get_cli_commands():
           return [
               GroupCommand(
                   name="my-executor",
                   help="Manage MyExecutor",
                   subcommands=[ActionCommand(name="ping", help="Ping", 
func=ping, args=())],
               )
           ]
   
   
   class MyCeleryExecutor(CeleryExecutor):
       """Custom executor built on a provider executor: inherits 
CeleryExecutor.get_cli_commands()."""
   EOF
   
   cat > files/demo/my_company/auth_managers.py <<'EOF'
   from airflow.api_fastapi.auth.managers.simple.simple_auth_manager import 
SimpleAuthManager
   from airflow.cli.cli_config import ActionCommand, GroupCommand
   from airflow.providers.fab.auth_manager.fab_auth_manager import 
FabAuthManager
   
   
   def whoami(args):
       print("hello from MyCustomAuthManager")
   
   
   class MyCustomAuthManager(SimpleAuthManager):
       """Plain custom auth manager, not packaged as a provider."""
   
       @staticmethod
       def get_cli_commands():
           return [
               GroupCommand(
                   name="my-auth",
                   help="Manage MyCustomAuthManager",
                   subcommands=[ActionCommand(name="whoami", help="Who am I", 
func=whoami, args=())],
               )
           ]
   
   
   class MyFabAuthManager(FabAuthManager):
       """Custom auth manager built on a provider auth manager: inherits 
FabAuthManager.get_cli_commands()."""
   EOF
   ```
   
   ```sh
   uv sync --frozen --project airflow-core
   export AIRFLOW_HOME="$(pwd)/files/demo/home"
   export AIRFLOW__CORE__LOAD_EXAMPLES=False
   export PYTHONPATH="$(pwd)/files/demo"
   ```
   
   2. Check custom executor
   
   ```sh
   AIRFLOW__CORE__EXECUTOR=my_company.executors.MyExecutor .venv/bin/airflow 
--help 2>/dev/null | grep -c my-executor
   AIRFLOW__CORE__EXECUTOR=my_company.executors.MyExecutor .venv/bin/airflow 
my-executor ping
   ```
   
   On main branch, the count is `0`. On this branch, it shows `1` and `pong 
from MyExecutor`.
   
   3. Check custom auth manager
   
   ```sh
   AIRFLOW__CORE__AUTH_MANAGER=my_company.auth_managers.MyCustomAuthManager 
.venv/bin/airflow --help 2>/dev/null | grep -c my-auth
   AIRFLOW__CORE__AUTH_MANAGER=my_company.auth_managers.MyCustomAuthManager 
.venv/bin/airflow my-auth whoami
   ```
   
   On main branch, the count is `0`. On this branch, it shows `1` and `hello 
from MyCustomAuthManager`.
   
   4. Check classes inheriting from a provider
   
   ```
   AIRFLOW__CORE__EXECUTOR=my_company.executors.MyCeleryExecutor 
.venv/bin/airflow --help 2>/dev/null | grep -cE '^\s+celery\s'
   AIRFLOW__CORE__AUTH_MANAGER=my_company.auth_managers.MyFabAuthManager 
.venv/bin/airflow --help 2>/dev/null | grep -cE '^\s+users\s'
   ```
   
   These two are regression guards rather than bug demos. On main they also 
print 1, because the fallback that imports the class and calls 
`get_cli_commands()` never runs there. What they prove is that re-opening that 
fallback does not start registering duplicate commands.
   
    <!-- 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]

Reply via email to