Skip to content
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .azure-pipelines/templates/automation_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ steps:
displayName: "Use Python ${{ parameters.pythonVersion }}"
- template: ./azdev_setup.yml
parameters:
EnableCompactAAZ: true
EnableCompactAAZ: false
- bash: |
set -ev

Expand Down
3 changes: 2 additions & 1 deletion src/azure-cli-core/azure/cli/core/aaz/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from azure.core.polling.base_polling import LocationPolling, StatusCheckPolling
from abc import abstractmethod

from ._poller import AAZNoPolling, AAZBasePolling
from azure.cli.core.cloud import (CloudEndpointNotSetException, CloudSuffixNotSetException,
CloudNameEnum as _CloudNameEnum)

Expand Down Expand Up @@ -111,6 +110,7 @@ def send_request(self, request, stream=False, **kwargs): # pylint: disable=argu
def build_lro_polling(self, no_wait, initial_session, deserialization_callback, error_callback,
lro_options=None, path_format_arguments=None):
from azure.core.polling.base_polling import OperationResourcePolling
from ._poller import AAZNoPolling, AAZBasePolling
if no_wait == True: # noqa: E712, pylint: disable=singleton-comparison
polling = AAZNoPolling()
else:
Expand Down Expand Up @@ -233,6 +233,7 @@ def _build_per_call_policies(cls, ctx, **kwargs):
def build_lro_polling(self, no_wait, initial_session, deserialization_callback, error_callback,
lro_options=None, path_format_arguments=None):
from azure.mgmt.core.polling.arm_polling import AzureAsyncOperationPolling, BodyContentPolling
from ._poller import AAZNoPolling, AAZBasePolling
if no_wait == True: # noqa: E712, pylint: disable=singleton-comparison
polling = AAZNoPolling()
else:
Expand Down
100 changes: 92 additions & 8 deletions src/azure-cli-core/azure/cli/core/aaz/_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from ._base import AAZUndefined, AAZBaseValue
from ._field_type import AAZObjectType
from ._paging import AAZPaged
from ._poller import AAZLROPoller
from ._command_ctx import AAZCommandCtx
from .exceptions import AAZUnknownFieldError, AAZUnregisteredArg
from .utils import get_aaz_profile_module_name
Expand Down Expand Up @@ -235,6 +234,7 @@ def processor(schema, result):
def build_lro_poller(self, executor, extract_result):
""" Build AAZLROPoller instance to support long running operation
"""
from ._poller import AAZLROPoller
polling_generator = executor()
if self.ctx.lro_no_wait:
# run until yield the first polling
Expand Down Expand Up @@ -389,20 +389,20 @@ def decorator(cls):

def load_aaz_command_table(loader, aaz_pkg_name, args):
""" This function is used in AzCommandsLoader.load_command_table.
It will load commands in module's aaz package.
It will load commands in module's aaz package using file-path based navigation.
"""
profile_pkg = _get_profile_pkg(aaz_pkg_name, loader.cli_ctx.cloud)

command_table = {}
command_group_table = {}
if args is None:
arg_str = ''
fully_load = True
if args is None or os.environ.get(AAZ_PACKAGE_FULL_LOAD_ENV_NAME, 'False').lower() == 'true':
effective_args = None # fully load
else:
arg_str = ' '.join(args).lower() # Sometimes args may contain capital letters.
fully_load = os.environ.get(AAZ_PACKAGE_FULL_LOAD_ENV_NAME, 'False').lower() == 'true' # disable cut logic
effective_args = list(args)
if profile_pkg is not None:
_load_aaz_pkg(loader, profile_pkg, command_table, command_group_table, arg_str, fully_load)
base_path = os.path.dirname(profile_pkg.__file__)
_load_aaz_by_path(loader, base_path, profile_pkg.__name__, effective_args,
command_table, command_group_table)

for group_name, command_group in command_group_table.items():
loader.command_group_table[group_name] = command_group
Expand Down Expand Up @@ -439,6 +439,90 @@ def _wrapper(cls):
return _wrapper


def _try_import_module(relative_name, package):
"""Try to import a module by relative name, return None on failure."""
try:
return importlib.import_module(relative_name, package)
except (ModuleNotFoundError, ImportError):
logger.debug('Failed to import module %s relative to %s.', relative_name, package)
return None


def _register_from_module(loader, mod, command_table, command_group_table):
"""Scan a module's namespace for AAZCommand/AAZCommandGroup classes and register them."""
for value in mod.__dict__.values():
if not isinstance(value, type):
continue
if issubclass(value, AAZCommandGroup) and value.AZ_NAME:
command_group_table[value.AZ_NAME] = value(cli_ctx=loader.cli_ctx)
elif issubclass(value, AAZCommand) and value.AZ_NAME:
command_table[value.AZ_NAME] = value(loader=loader)


def _load_aaz_by_path(loader, base_path, base_module, args, command_table, command_group_table):
"""Recursively navigate the AAZ package tree guided by CLI args.

- args is None or empty → full recursive load of all commands under this directory.
- args has items → try to match first arg as a command file or sub-directory,
recurse with remaining args on match.
- args exhausted / no match → load current level's commands and sub-group headers.

:param base_path: Filesystem path of the current package directory.
:param base_module: Dotted module name of the current package.
:param args: Remaining CLI args (list of str), or None for full load.
"""
if not os.path.isdir(base_path):
return

if args is not None and args and not args[0].startswith('-'):
first_arg = args[0].lower().replace('-', '_')

# First arg matches a command file (e.g. "create" → "_create.py")
cmd_file = os.path.join(base_path, f"_{first_arg}.py")
if os.path.isfile(cmd_file):
mod = _try_import_module(f"._{first_arg}", base_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)
return

# First arg matches a sub-directory (command group)
sub_dir = os.path.join(base_path, first_arg)
if os.path.isdir(sub_dir):
sub_module = f"{base_module}.{first_arg}"
mod = _try_import_module('.__cmd_group', sub_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)
_load_aaz_by_path(loader, sub_dir, sub_module, args[1:], command_table, command_group_table)
return

# Load __cmd_group + all command files at this level
mod = _try_import_module('.__cmd_group', base_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)

for entry in os.listdir(base_path):
entry_path = os.path.join(base_path, entry)

# Command files: _create.py, _list.py, etc.
if (entry.startswith('_') and not entry.startswith('__')
and entry.endswith('.py') and os.path.isfile(entry_path)):
mod = _try_import_module(f'.{entry[:-3]}', base_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)

# Sub-directories
elif not entry.startswith('_') and os.path.isdir(entry_path):
sub_module = f"{base_module}.{entry}"
if not args:
# Full load → recurse into every sub-directory
_load_aaz_by_path(loader, entry_path, sub_module, None, command_table, command_group_table)
else:
# Args exhausted / not matched → only load sub-group headers for help listing
mod = _try_import_module('.__cmd_group', sub_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)


def _load_aaz_pkg(loader, pkg, parent_command_table, command_group_table, arg_str, fully_load):
""" Load aaz commands and aaz command groups under a package folder.
"""
Expand Down
3 changes: 0 additions & 3 deletions src/azure-cli-core/azure/cli/core/aaz/_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
from azure.core.polling.base_polling import LROBasePolling
from azure.core.tracing.common import with_current_context
from azure.core.tracing.decorator import distributed_trace
# import requests in main thread to resolve import deadlock between threads in python
# reference https://github.com/psf/requests/issues/2925 and https://github.com/Azure/azure-cli/issues/26272
import requests # pylint: disable=unused-import

_LOGGER = logging.getLogger(__name__)

Expand Down
10 changes: 10 additions & 0 deletions src/azure-cli-core/azure/cli/core/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ def load_command_table(self, command_loader):
_argument_validators=argument_validators,
_parser=command_parser)

# Ensure subparsers are created for all registered command groups, even
# those that have no commands in the (possibly truncated) command table.
# Without this, empty command groups would not appear in help output.
for group_name in grp_tbl:
# _get_subparser creates intermediate subparsers for path[:1] through
# path[:len-1]. Append a sentinel so the full group path is treated
# as an intermediate level and its subparser is created.
path = group_name.split() + ['_placeholder']
self._get_subparser(path, grp_tbl)

def validation_error(self, message):
az_error = ValidationError(message)
az_error.print_error()
Expand Down
10 changes: 10 additions & 0 deletions src/azure-cli/azure/cli/command_modules/network/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ def load_command_table(self, args):
aaz_pkg_name=aaz.__name__,
args=args
)
try:
from . import operations
except ImportError:
operations = None
if operations:
load_aaz_command_table(
loader=self,
aaz_pkg_name=operations.__name__,
args=args
)
load_command_table(self, args)
return self.command_table

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# pylint: disable=inconsistent-return-statements
@Completer
def subnet_completion_list(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument
from .aaz.latest.network.vnet.subnet import List
from .aaz.latest.network.vnet.subnet._list import List
if namespace.resource_group_name and namespace.virtual_network_name:
rg = namespace.resource_group_name
vnet = namespace.virtual_network_name
Expand All @@ -27,7 +27,7 @@ def get_lb_subresource_completion_list(prop):
# pylint: disable=inconsistent-return-statements
@Completer
def completer(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument
from .aaz.latest.network.lb import Show
from .aaz.latest.network.lb._show import Show
try:
lb_name = namespace.load_balancer_name
except AttributeError:
Expand All @@ -46,7 +46,7 @@ def get_ag_subresource_completion_list(prop):
# pylint: disable=inconsistent-return-statements
@Completer
def completer(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument
from .aaz.latest.network.application_gateway import Show
from .aaz.latest.network.application_gateway._show import Show
try:
ag_name = namespace.application_gateway_name
except AttributeError:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ def get_network_watcher_from_location(remove=False, watcher_name='watcher_name',
rg_name='watcher_rg'):
def _validator(cmd, namespace):
from azure.mgmt.core.tools import parse_resource_id
from .aaz.latest.network.watcher import List
from .aaz.latest.network.watcher._list import List

location = namespace.location
watcher_list = List(cli_ctx=cmd.cli_ctx)(command_args={})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._list_service_aliases import *
from ._list_service_tags import *
from ._list_usages import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._health import *
from ._health_on_demand import *
from ._list import *
from ._show import *
from ._start import *
from ._stop import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._add import *
from ._list import *
from ._remove import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._assign import *
from ._show import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._add import *
from ._list import *
from ._remove import *
from ._show import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._add import *
from ._list import *
from ._remove import *
from ._show import *
from ._wait import *
Loading
Loading